天天看點

Linux學習總結(四):Linux下使用GCC編譯

本文經野火《i.MX Linux開發實戰指南》整理而來。

文章目錄

    • 1.Ubuntu16.04系統下的HelloWorld
      • (1)建立工作目錄
      • (2)編寫代碼檔案
      • (3)編譯并執行
    • 2.GCC介紹
      • (1)GCC工具鍊元件
      • (2)基本指令文法
      • (3)GCC編譯過程
      • (4)連結過程特别說明

1.Ubuntu16.04系統下的HelloWorld

(1)建立工作目錄

mkdir -p ~/workdir/example/hello_c
           

(2)編寫代碼檔案

使用vscode編寫以下代碼并儲存

#include <stdio.h>

int main() 
{
	printf("hello, world! This is a C program.\n");
	for(int i=0;i<10;i++ ){
		printf("output i=%d\n",i);
	}

	return 0;
}
           

(3)編譯并執行

#在目标檔案目錄下執行指令
gcc hello.c –o hello #使用gcc把hello.c編譯成hello程式

ls        #檢視目錄下的檔案
./hello   #執行生成的hello程式
#若提示權限不夠或不是可執行檔案,執行如下指令再運作hello程式
chmod u+x hello #給hello檔案添加可執行權限
           

2.GCC介紹

(1)GCC工具鍊元件

GCC編譯工具鍊是指以GCC編譯器為核心的一套工具,用于把源代碼轉化成可執行程式,主要包含以下三部分:

  • gcc-core:即GCC編譯器,用于完成預處理和編譯過程,例如把C代碼轉換成彙編代碼。
  • Binutils :除GCC編譯器外的一系列小工具包括了連結器ld,彙編器as、目标檔案格式檢視器readelf等。
  • glibc:包含了主要的 C語言标準函數庫,C語言中常常使用的列印函數printf、malloc函數就在glibc 庫中。

(2)基本指令文法

gcc  [選項]  輸入的檔案名
           

常用選項:

  • -o:小寫字母”o”,指定生成的可執行檔案的名字,不指定的話生成的可執行檔案名為a.out。
  • -E:隻進行預處理,既不編譯,也不彙編。
  • -S:隻編譯,不彙編。
  • -c:編譯并彙編,但不進行連結。
  • -g:生成的可執行檔案帶調試資訊,友善使用gdb進行調試。
  • -Ox:大寫字母”O”加數字,設定程式的優化等級,如”-O0”“-O1” “-O2” “-O3”, 數字越大代碼的優化等級越高,編譯出來的程式一般會越小,但有可能會導緻程式不正常運作。

(3)GCC編譯過程

GCC 編譯工具鍊在編譯一個C源檔案時需要經過以下 4 步:

  • 預處理:對源代碼檔案中的檔案包含(include)、 預編譯語句(如宏定義define等)進行展開,生成.i檔案。可了解為把頭檔案的代碼、宏之類的内容轉換成更純粹的C代碼,頭檔案中引用的内容彙總到一處,生成的檔案以.i為字尾。
  • 編譯,把預處理後的.i檔案通過編譯成為彙編語言,生成.s檔案,即把代碼從C語言轉換成彙編語言(編譯器)
  • 彙編,将彙編語言檔案經過彙編,生成目标檔案的.o檔案,每一個源檔案都對應一個目标檔案。即把彙編語言的代碼轉換成機器碼(彙編器)。
  • 連結,将每個源檔案對應的.o檔案連結起來,就生成一個可執行程式檔案(連結器)。
#直接編譯成可執行檔案
gcc hello.c -o hello

#以上指令等價于執行以下全部操作
#預處理,可了解為把頭檔案的代碼彙總成C代碼,把*.c轉換得到*.i檔案
gcc –E hello.c –o hello.i

#編譯,可了解為把C代碼轉換為彙編代碼,把*.i轉換得到*.s檔案
gcc –S hello.i –o hello.s

#彙編,可了解為把彙編代碼轉換為機器碼,把*.s轉換得到*.o,即目标檔案
gcc –c hello.s –o hello.o

#連結,把不同檔案之間的調用關系連結起來,把一個或多個*.o轉換成最終的可執行檔案
gcc hello.o –o hello
           

Linux下生成的*.o目标檔案、*.so動态庫檔案以及連結生成的可執行檔案都是elf格式的, 可以使用”readelf”工具來檢視它們的内容:

#在檔案目錄執行指令
readelf -a hello.o
           

(4)連結過程特别說明

例如一個工程裡包含了A和B兩個代碼檔案,編譯後生成了A.o和B.o目标檔案, 如果在代碼A中調用了B中的某個函數fun,那麼在A的代碼中隻要包含了fun的函數聲明, 編譯就會通過,而不管B中是否真的定義了fun函數(當然,如果函數聲明都沒有,編譯也會報錯)。 也就是說A.o和B.o目标檔案在編譯階段是獨立的,而在連結階段, 連結過程需要把A和B之間的函數調用關系理順,也就是說要告訴A在哪裡能夠調用到fun函數, 建立映射關系,是以稱之為連結。若連結過程中找不到fun函數的具體定義,則會連結報錯。

連結分為兩種:

  • 動态連結:GCC編譯時的預設選項。動态是指在應用程式運作時才去加載外部的代碼庫,例如printf函數的C标準代碼庫*.so檔案存儲在Linux系統的某個位置,hello程式執行時調用庫檔案*.so中的内容,不同的程式可以共用代碼庫。 是以動态連結生成的程式比較小,占用較少的記憶體。
  • 靜态連結,連結時使用選項”–static”,它在編譯階段就會把所有用到的庫打包到自己的可執行程式中。靜态連結的優點是具有較好的相容性,不依賴外部環境,但是生成的程式比較大。
#動态連結,生成名為hello的可執行檔案
gcc hello.o –o hello
#靜态連結,使用--static參數,生成名為hello_static的可執行檔案
gcc hello.o –o hello_static --static
           

在Ubuntu下,可以使用ldd工具檢視動态檔案的庫依賴,而靜态檔案沒有庫依賴:

#在hello所在的目錄執行如下指令
ldd hello
ldd hello_static
           

繼續閱讀