編譯系統GCC

4.1 GCC
在 Linux 平臺上,最流行的編譯系統是 GCC(GNU Compile Collection)。GCC 也是 GNU
發佈的最著名的軟件之一。GCC 的功能非常強大,主要體現在兩方面。
1) GCC 可以爲 x86、ARM、MIPS 等不同體系結構的硬件平臺編譯程序。
2) GCC 可以編譯 C、C++、Pascal、Java 等數十種高級語言。
GCC 的這兩項特性對嵌入式應用開發及其重要。此外,GCC 的編譯效率也是非常高的,
一般要高出其他編譯系統 20%到 30%左右。所以在嵌入式 Linux 開發領域,使用的基本上就
是 GCC 編譯系統。
在紅旗 Linux 6.0 系統中已經集成了 GCC,通過命令“gcc -v” 可以查看 GCC 中 C 編
譯器 gcc 的詳細信息。
[root@localhost ~]#gcc -v
\Using built-in specs.
Target: x86-pc-linux
Configured with: ../configure –prefix=/usr –mandir=/usr/share/man
–infodir=/usr/share/info –enable-shared –enable-threads=posix
–enable-checking=release –with-system-zlib –enable-__cxa_atexit
–disable-libunwind-exceptions –enable-libgcj-multifile –disable-libmudflap
–enable-languages=c,c++,java,fortran –disable-libgcj –with-cpu=generic
–host=x86-pc-linux
Thread model: posix
gcc version 4.2.1
gcc 命令的使用格式爲:
gcc [選項] [文件名] [選項] [文件名]
gcc 命令擁有數量龐大的編譯選項,按類型可以把選項分爲以下幾大類。
 總體選項:用於控制編譯的整個流程。
常用選項:
-c:對源文件進行編譯或彙編。
-E:對源文件進行預處理。
-S:對源文件進行編譯。
-o file:輸出目標文件 file。
-v:顯示編譯階段的命令。
 語言選項:用於支持各種版本的 C 語言程序。
常用選項:
-ansi:支持符合 ANSI 標準的 C 程序。
 警告選項:用於控制編譯過程中產生的各種警告信息。
常用選項:
-W:屏蔽所有的警告信息。
-Wall:顯示所有類型的警告信息。
-Werror:出現任何警告信息就停止編譯。
71
 調試選項:用於控制調試信息。
常用選項:
-g:產生調試信息。
 優化選項:用於對目標文件進行優化。
常用選項:
-O1:對目標文件的性能進行優化。
-O2:在-O1 的基礎上進一步優化,提高目標文件的運行性能。
-O3:在-O2 的基礎上進一步優化,支持函數集成優化。
-O0:不進行優化。
 連接器選項:用於控制鏈接過程。
常用選項:
-static:使用靜態鏈接。
-llibrary:鏈接 library 函數庫文件。
-L dir:指定連接器的搜索目錄 dir。
-shared:生成共享文件。
 目錄選項:用於指定編譯器的文件搜索目錄。
常用選項:
-Idir:指定頭文件的搜索目錄 dir。
-Ldir:指定搜索目錄 dir。
此外,還有配置選項等其他選項,這裏不做介紹了。
編譯系統本身是一種相當複雜的程序,編寫甚至讀懂這樣的程序都是非常困難的。但
是從事嵌入式 Linux 應用的開發人員都應掌握編譯系統的基本原理和工作流程。
4.1.1 GCC 工作流程
在 C 程序的編譯過程中,依次要進行預處理、編譯、彙編、鏈接四個階段。這裏通過編
譯 C 文件 test.c 來展示 GCC 的工作流程。
例如:
 test.c

include

820 “/usr/include/stdio.h” 3 4

extern void flockfile (FILE *__stream) attribute ((nothrow));
extern int ftrylockfile (FILE *__stream) attribute ((nothrow)) ;
extern void funlockfile (FILE *__stream) attribute ((nothrow));

850 “/usr/include/stdio.h” 3 4

2 “test.c” 2

int main()
{
printf(“Hello world!\n”);
return 0;
}
2. 編譯階段
編譯階段是整個編譯過程中最複雜的一個階段。這裏拿自然語言的翻譯過程作個對比。
比如在把“I love China”翻譯成中文前,需要依次完成以下幾個步驟:
1)考察這個句子中每個單詞的拼寫是不是正確。
2)考察整個句子的語法(比如主謂賓、定狀補的結構等)是不是正確。
3)考察整個句子的語義是不是正確。
只有以上三個步驟都正常通過了,才能保證句子被正確翻譯。同樣,高級編程語言的編
譯階段也必須實現這三個步驟。
1) 步驟 1 稱爲詞法分析,主要負責檢查關鍵字、標識符等是否正確。
2) 步驟 2 稱爲語法分析,主要負責檢查程序中語句的語法是否正確。
3) 步驟 3 稱爲語義分析,主要負責檢查程序中語句的邏輯意義是否正確。
在 shell 中輸入命令“gcc -S test.i -o test.s”。其中,參數 S 告訴 gcc 命令只進行
編譯,不做其他處理。命令運行完畢後就會產生一個名爲 test.s 的彙編文件。如下所示:
[root@localhost home]#gcc -S test.i -o test.s
[root@localhost home]#ls
test.c test.i test.s
在學習使用彙編語言編程的時候,對照 C 文件和其彙編程序是很好的辦法。如下所示的
是 test.s 的代碼。
.file “test.c”
.section .rodata
.LC0:
.string “Hello world!”
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
73
andl 16,pushl4(pushlmovlpushlsubl 4, %esp
movl .LC0,(callputsmovl 0, %eax
addl $4, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident “GCC: (GNU) 4.2.1”
.section .note.GNU-stack,”“,@progbits
注意,以上所示的彙編代碼是針對 x86 平臺的。
3. 彙編階段
彙編階段的任務是把彙編程序翻譯成 CPU 可以識別的二進制文件,該文件又稱爲目標
文件。
在 shell 中輸入命令“gcc -c test.s -o test.o”,其中,參數 c 告訴 gcc 命令只進
行彙編,不做其他處理。命令運行完畢後就會產生一個名爲 test.o 的目標文件。如下所示:
[root@localhost home]#gcc -c test.s -o test.o
[root@localhost home]#ls
test.c test.i test.o test.s
在 Windows 系統中,目標文件的後綴是 obj。
4. 鏈接階段
目標文件雖然已經可以被 CPU 直接識別,但是單個目標文件一般是無法運行的。原因
在於一個程序往往是由多個源文件組成的,每個源文件只對應一個目標文件。也許有人會問,
test 程序不就只有一個源文件 test.c 嗎,爲什麼也不能直接運行呢?原因是 test.c 使用
了 stdio.h 對應的庫函數,所以必須要把 test.o 文件和函數庫文件鏈接在一起才能運行。
鏈接階段的任務就是把程序中所有的目標文件和所需的庫文件都鏈接在一起,最終生
成一個可以直接運行的文件,稱爲可執行文件。
在 shell 中輸入命令“gcc test.o -o test”,運行完畢後就會產生一個名爲 test 的
可執行文件。輸入命令“./test”執行該文件,就可以得到 test 文件的運行結果“Hello
world!”。 如下所示:
[root@localhost home]#gcc test.o -o test
[root@localhost home]#./test
Hello world!
gcc 命令生成的可執行文件的有以下三種格式。
1)a.out(Assembler and Link editor output);
2)COFF(Common object file format);
3)ELF(Executable and linkable format);
74
其中,a.out 和 COFF 格式都是比較老的格式,現在 Linux 平臺上可執行文件的主流格
式是 ELF。
4.1.2 Glibc
從邏輯功能上看,程序的主體是由一系列函數組成的。所以說編寫程序的主要工作之一
就是實現函數。爲了有效降低編程的工作量,編程系統會把一些非常基本、常用的函數集中
到函數庫中實現。比如信息的打印函數、文件的打開或關閉函數、內存空間的申請與釋放函
數、數學計算函數等。當程序需要使用到函數庫中的某個函數時,就可以直接從庫中調用。
就好比建造房屋,建築隊並不需要從頭開始製造磚瓦和水泥,而只需要從原材料市場購買就
可以了。
每種高級編程語言都有各自的函數庫。比如 C 語言的 C 庫、Visual C++的 MFC、Java
的 JFC 等。函數庫中的函數都是由經驗豐富的資深程序員編寫的,具有出色的運行性能和工
作效率。所以說,函數庫的使用不光減少了編程的工作量,還能有效提高程序的性能和健壯
性。在面嚮對象語言中,函數被封裝在類中,所以函數庫就演變成了類庫,但其原理和機制
是類似的。
函數庫的使用方式分爲靜態鏈接和動態鏈接兩種。靜態鏈接是指編譯系統在鏈接階段把
程序的目標文件和所需的函數庫文件鏈接在一起,這樣生成的可執行文件就可以在沒有函數
庫的情況下運行。就好比火箭把燃料和氧料裝在一起,就可以在沒有空氣的太空中飛行。動
態鏈接是指編譯系統在鏈接階段並不把目標文件和函數庫文件鏈接在一起,而是等到程序在
運行過程中需要使用時才鏈接函數庫。
使用靜態鏈接方式產生的可執行文件體積較大,但運行效率較高。而使用動態鏈接方式
產生的可執行文件由於沒有庫文件,所以體積較小。但由於需要動態加載函數庫,所以運行
效率要低一點。
在具體應用時,如果有多個源文件都需要調用函數庫時,那麼應該選擇動態鏈接的方式。
而只有少數源文件需要調用函數庫時,應該選擇靜態鏈接的方式。可以被靜態鏈接的函數庫
稱爲靜態庫,可以被動態鏈接的函數庫稱爲動態庫,或者共享庫。
Glibc(GNU Library C)是 GNU 推出的 C 語言函數庫。Glibc 符合 ISO C (International
Standard for the C programming language)和 POSIX(Portable Operating System
Interface for Computer Environments)標準。其中,ISO C 定義了 C 函式庫的標準格式,
POSIX 定義了不同計算平臺應該遵守的 C 函數庫標準,是 ISO C 標準的擴充。因此 Glibc 可
以在各種不同體系結構的計算平臺上使用。
Glibc 中包含了大量的函數庫,其中 libc 是最基本的函數庫,每個 C 程序都需要使用
libc 庫。此外,常用的還有數學庫 libm、加密庫 libcrypt、POSIX 線程庫 libpthread、網
絡服務庫 libnsl、IEEE 浮點運算庫 libieee 等。Glibc 庫爲 C 程序提供了大量功能強大的
函數,包括輸入輸出函數、字符串處理函數、數學函數、中斷處理函數、錯誤處理函數、日
期時間函數等等。
C 程序在調用 Glibc 中的函數庫時,需要引用與函數庫對應的頭文件。比如 stdio.h、
string.h、time.h 等。這些頭文件都存放在/usr/include 目錄下。同時,在編譯命令中需
要加入某些函數庫的鏈接參數(在函數庫的使用文檔中會列出具體的鏈接庫名稱參數),並
使用符號“-l”進行連接。比如 libm 庫的鏈接參數爲 m,libpthread 庫的鏈接參數爲 pthread
等。
例如:
 test.c:

include

include

發佈了38 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章