一框都、GCC編譯過(guò)程
在使用gcc編譯程序時(shí),編譯過(guò)程可以細(xì)分為4個(gè)階段:
- 預(yù)處理(Pre-Processing)
- 編譯(Compiling)
- 匯編(Assembling)
-
鏈接(Linking)
編譯過(guò)程
二、使用GCC
查看GCC版本
gcc -v
各個(gè)編譯階段
這里引入一個(gè)例子
// hello.c----------------------------
#include <stdio.h>
int main (int argc,char **argv) {
printf("Hello Linux\n");
}
- 編譯這個(gè)程序,只要在命令行下執(zhí)行如下命令:
gcc hello.c -o hello
./hello
- 采用模塊化設(shè)計(jì)中瓜贾,編譯多個(gè)C文件
gcc david.c xueer.c -o davidxueer
-
(1)預(yù)編譯
使用-E
參數(shù)可以讓gcc在預(yù)處理結(jié)束后停止編譯過(guò)程:
gcc -E hello.c -o hello.i
此時(shí)若查看hello.i文件中的內(nèi)容,會(huì)發(fā)現(xiàn)stdio.h的內(nèi)容確實(shí)都插到文件里去了携悯,而且被預(yù)處理的宏定義也都作了相應(yīng)的處理祭芦。
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
# 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 424 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
...
-
(2)編譯
編譯過(guò)程通過(guò)詞法和語(yǔ)法分析,確認(rèn)所有指令符合語(yǔ)法規(guī)則(否則報(bào)編譯錯(cuò)),之后翻譯成對(duì)應(yīng)的中間碼憔鬼,在linux中被稱(chēng)為RTL(Register Transfer Language)龟劲,通常是平臺(tái)無(wú)關(guān)的,這個(gè)過(guò)程也被稱(chēng)為編譯前端轴或。編譯后端對(duì)RTL樹(shù)進(jìn)行裁減昌跌,優(yōu)化,得到在目標(biāo)機(jī)上可執(zhí)行的匯編代碼照雁。gcc采用as作為其匯編器蚕愤,所以匯編碼是AT&T格式的,而不是Intel格式饺蚊,所以在用gcc編譯嵌入式匯編時(shí)审胸,也要采用AT&T格式。
使用-S
命令
gcc -S hello.i -o hello.S
gcc默認(rèn)將.i文件看成是預(yù)處理后的C語(yǔ)言源代碼卸勺,因此上述命令將自動(dòng)跳過(guò)預(yù)處理步驟而開(kāi)始執(zhí)行編譯過(guò)程砂沛。
也可以使用-x參數(shù)讓gcc從指定的步驟開(kāi)始編譯為目標(biāo)代碼。
以下為編譯后的輸出文件hello.s的內(nèi)容
.file "hello.c"
.text
.section .rodata
.LC0:
.string "hello linux"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0"
.section .note.GNU-stack,"",@progbits
-
(3)匯編
匯編器是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的命令曙求,每一個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器指令碍庵。匯編相對(duì)于編譯過(guò)程比較簡(jiǎn)單,根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一翻譯即可悟狱。
使用-c
命令
gcc –c hello.c –o hello.o
- (4)鏈接
gcc hello.o -o hello
鏈接器ld將各個(gè)目標(biāo)文件組裝在一起静浴,解決符號(hào)依賴,庫(kù)依賴關(guān)系挤渐,并生成可執(zhí)行文件苹享。
ld –static crt1.o crti.o crtbeginT.ohello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o
(省略了文件的路徑名)。
當(dāng)然鏈接的時(shí)候還會(huì)用到靜態(tài)鏈接庫(kù),和動(dòng)態(tài)連接庫(kù)得问。靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)都是.o目標(biāo)文件的集合囤攀。
靜態(tài)庫(kù)是在鏈接過(guò)程中將相關(guān)代碼提取出來(lái)加入可執(zhí)行文件的庫(kù)(即在鏈接的時(shí)候?qū)⒑瘮?shù)的代碼將從其所在地靜態(tài)鏈接庫(kù)中被拷貝到最終的可執(zhí)行程序中),ar只是將一些別的文件集合到一個(gè)文件中宫纬》倌樱可以打包,當(dāng)然也可以解包漓骚。
ar -v -q test.a test.o
上面指令可以生成靜態(tài)鏈接庫(kù)test.a
動(dòng)態(tài)庫(kù)在鏈接時(shí)只創(chuàng)建一些符號(hào)表蝌衔,而在運(yùn)行的時(shí)候才將有關(guān)庫(kù)的代碼裝入內(nèi)存,映射到運(yùn)行時(shí)相應(yīng)進(jìn)程的虛地址空間蝌蹂。如果出錯(cuò)噩斟,如找不到對(duì)應(yīng)的.so文件,會(huì)在執(zhí)行的時(shí)候報(bào)動(dòng)態(tài)連接錯(cuò)(可用LD_LIBRARY_PATH指定路徑)孤个。用file test.so
可以看到test.so是shared object的ELF文件剃允。
gcc -sharedtest.so test.o
上面指令可以生成動(dòng)態(tài)連接庫(kù)test.so
gcc警告提示功能
- 當(dāng)gcc在編譯不符合ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)的源代碼時(shí),如果加上了
-pedantic
選項(xiàng)硼身,那么使用了擴(kuò)展語(yǔ)法的地方將產(chǎn)生相應(yīng)的警告信息:
gcc -pedantic bad.c -o bad
輸出
bad.c: In function 'main':
bad.c:4: warning: ISO C89 does not support 'long long'
bad.c:3: warning: return type of 'main' is not 'int'
需要注意的是硅急,-pedantic編譯選項(xiàng)并不能保證被編譯程序與ANSI/ISO C標(biāo)準(zhǔn)的完全兼容,它僅僅用來(lái)幫助Linux程序員離這個(gè)目標(biāo)越來(lái)越近佳遂。換句話說(shuō)营袜,-pedantic選項(xiàng)能夠幫助程序員發(fā)現(xiàn)一些不符合ANSI/ISO C標(biāo)準(zhǔn)的代碼,但不是全部丑罪。事實(shí)上只有ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)中要求進(jìn)行編譯器診斷的那些問(wèn)題才有可能被gcc發(fā)現(xiàn)并提出警告荚板。
- 除了-pedantic之外,gcc還有一些其他編譯選項(xiàng)也能夠產(chǎn)生有用的警告信息吩屹。這些選項(xiàng)大多以-W開(kāi)頭跪另,其中最有價(jià)值的當(dāng)數(shù)
-Wall
了,使用它能夠使gcc產(chǎn)生盡可能多的警告信息煤搜。例如:
gcc -Wall bad.c -o bad
輸出
bad.c:3: warning: return type of 'main' is not 'int'
bad.c: In function 'main':
bad.c:4: warning: unused variable 'var'
bad.c:6:2: warning: no newline at end of file
gcc給出的警告信息雖然從嚴(yán)格意義上說(shuō)不能算作是錯(cuò)誤免绿,但很可能成為錯(cuò)誤的棲身之所。一個(gè)優(yōu)秀的Linux程序員應(yīng)該盡量避免產(chǎn)生警告信息擦盾,使自己的代碼始終保持簡(jiǎn)潔嘲驾、優(yōu)美和健壯的特性。
- 在處理警告方面迹卢,另一個(gè)常用的編譯選項(xiàng)是
-Werror
辽故,它要求gcc將所有的警告當(dāng)成錯(cuò)誤進(jìn)行處理,這在使用自動(dòng)編譯工具(如make等)時(shí)非常有用腐碱。如果編譯時(shí)帶上-Werror選項(xiàng)誊垢,那么gcc會(huì)在所有產(chǎn)生警告的地方停止編譯,迫使程序員對(duì)自己的代碼進(jìn)行修改。只有當(dāng)相應(yīng)的警告信息消除時(shí)喂走,才可能將編譯過(guò)程繼續(xù)朝前推進(jìn)殃饿。
gcc -Werror bad.c -o bad
對(duì)Linux程序員來(lái)講,gcc給出的警告信息是很有價(jià)值的缴啡,它們不僅可以幫助程序員寫(xiě)出更加健壯的程序壁晒,而且還是跟蹤和調(diào)試程序的有力工具瓷们。建議在用gcc編譯源代碼時(shí)始終帶上-Wall選項(xiàng)业栅,并把它逐漸培養(yǎng)成為一種習(xí)慣,這對(duì)找出常見(jiàn)的隱式編程錯(cuò)誤很有幫助谬晕。
gcc代碼優(yōu)化
編譯時(shí)使用選項(xiàng)-O可以告訴gcc同時(shí)減小代碼的長(zhǎng)度和執(zhí)行時(shí)間碘裕,其效果等價(jià)于-O1
。在這一級(jí)別上能夠進(jìn)行的優(yōu)化類(lèi)型雖然取決于目標(biāo)處理器攒钳,但一般都會(huì)包括線程跳轉(zhuǎn)(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優(yōu)化帮孔。
選項(xiàng)-O2
告訴gcc除了完成所有-O1級(jí)別的優(yōu)化之外,同時(shí)還要進(jìn)行一些額外的調(diào)整工作不撑,如處理器指令調(diào)度等文兢。
選項(xiàng)-O3
則除了完成所有-O2級(jí)別的優(yōu)化之外,還包括循環(huán)展開(kāi)和其他一些與處理器特性相關(guān)的優(yōu)化工作焕檬。
通常來(lái)說(shuō)姆坚,數(shù)字越大優(yōu)化的等級(jí)越高,同時(shí)也就意味著程序的運(yùn)行速度越快实愚。許多Linux程序員都喜歡使用-O2選項(xiàng)兼呵,因?yàn)樗趦?yōu)化長(zhǎng)度、編譯時(shí)間和代碼大小之間取得了一個(gè)比較理想的平衡點(diǎn)腊敲。
- 借助Linux提供的time命令击喂,可以大致統(tǒng)計(jì)出該程序在運(yùn)行時(shí)所需要的時(shí)間:
$ time ./test
由此可以測(cè)試優(yōu)化后代碼的運(yùn)行性能
優(yōu)化雖然能夠給程序帶來(lái)更好的執(zhí)行性能,但在如下一些場(chǎng)合中應(yīng)該避免優(yōu)化代碼碰辅。
● 程序開(kāi)發(fā)的時(shí)候:優(yōu)化等級(jí)越高懂昂,消耗在編譯上的時(shí)間就越長(zhǎng),因此在開(kāi)發(fā)的時(shí)候最好不要使用優(yōu)化選項(xiàng)没宾,只有到軟件發(fā)行或開(kāi)發(fā)結(jié)束的時(shí)候凌彬,才考慮對(duì)最終生成的代碼進(jìn)行優(yōu)化。
● 資源受限的時(shí)候:一些優(yōu)化選項(xiàng)會(huì)增加可執(zhí)行代碼的體積榕吼,如果程序在運(yùn)行時(shí)能夠申請(qǐng)到的內(nèi)存資源非常緊張(如一些實(shí)時(shí)嵌入式設(shè)備)饿序,那就不要對(duì)代碼進(jìn)行優(yōu)化,因?yàn)橛蛇@帶來(lái)的負(fù)面影響可能會(huì)產(chǎn)生非常嚴(yán)重的后果羹蚣。
● 跟蹤調(diào)試的時(shí)候:在對(duì)代碼進(jìn)行優(yōu)化的時(shí)候原探,某些代碼可能會(huì)被刪除或改寫(xiě),或者為了取得更佳的性能而進(jìn)行重組,從而使跟蹤和調(diào)試變得異常困難咽弦。
加速
在將源代碼變成可執(zhí)行文件的過(guò)程中徒蟆,需要經(jīng)過(guò)許多中間步驟,包含預(yù)處理型型、編譯段审、匯編和連接。這些過(guò)程實(shí)際上是由不同的程序負(fù)責(zé)完成的闹蒜。大多數(shù)情況下gcc可以為L(zhǎng)inux程序員完成所有的后臺(tái)工作寺枉,自動(dòng)調(diào)用相應(yīng)程序進(jìn)行處理。
這樣做有一個(gè)很明顯的缺點(diǎn)绷落,就是gcc在處理每一個(gè)源文件時(shí)姥闪,最終都需要生成好幾個(gè)臨時(shí)文件才能完成相應(yīng)的工作,從而無(wú)形中導(dǎo)致處理速度變慢砌烁。例如筐喳,gcc在處理一個(gè)源文件時(shí),可能需要一個(gè)臨時(shí)文件來(lái)保存預(yù)處理的輸出函喉,一個(gè)臨時(shí)文件來(lái)保存編譯器的輸出避归,一個(gè)臨時(shí)文件來(lái)保存匯編器的輸出,而讀寫(xiě)這些臨時(shí)文件顯然需要耗費(fèi)一定的時(shí)間管呵。當(dāng)軟件項(xiàng)目變得非常龐大的時(shí)候梳毙,花費(fèi)在這上面的代價(jià)可能會(huì)變得很大。
解決的辦法是撇寞,使用Linux提供的一種更加高效的通信方式——管道顿天。它可以用來(lái)同時(shí)連接兩個(gè)程序,其中一個(gè)程序的輸出將直接作為另一個(gè)程序的輸入蔑担,這樣就可以避免使用臨時(shí)文件牌废,但編譯時(shí)卻需要消耗更多的內(nèi)存。
注意:
在編譯過(guò)程中使用管道是由gcc的-pipe選項(xiàng)決定的啤握。下面的這條命令就是借助gcc的管道功能來(lái)提高編譯速度的:
gcc -pipe david.c -o david
在編譯小型工程時(shí)使用管道鸟缕,編譯時(shí)間上的差異可能還不是很明顯县爬,但在源代碼非常多的大型工程中溉卓,差異將變得非常明顯。
gcc常用選項(xiàng)
選 項(xiàng) 名 | 作 用 |
---|---|
-c | 通知gcc取消連接步驟没陡,即編譯源碼并在最后生成目標(biāo)文件 |
-Dmacro | 定義指定的宏蹲蒲,使它能夠通過(guò)源碼中的#ifdef進(jìn)行檢驗(yàn) |
-E | 不經(jīng)過(guò)編譯預(yù)處理程序的輸出而輸送至標(biāo)準(zhǔn)輸出 |
-g3 | 獲得有關(guān)調(diào)試程序的詳細(xì)信息番甩,它不能與-o選項(xiàng)聯(lián)合使用 |
-Idirectory | 在包含文件搜索路徑的起點(diǎn)處添加指定目錄 |
-llibrary | 提示連接程序在創(chuàng)建最終可執(zhí)行文件時(shí)包含指定的庫(kù) |
-O -O2 -O3 | 將優(yōu)化狀態(tài)打開(kāi),該選項(xiàng)不能與-g選項(xiàng)聯(lián)合使用届搁。當(dāng)出現(xiàn)多個(gè)優(yōu)化時(shí),以最后一個(gè)為準(zhǔn) |
-O0 | 關(guān)閉所有優(yōu)化選項(xiàng) |
-S | 要求編譯程序生成來(lái)自源代碼的匯編程序輸出 |
-v | 啟動(dòng)所有警報(bào) |
.h | 預(yù)處理文件(標(biāo)頭文件) |
-Wall | 在發(fā)生警報(bào)時(shí)取消編譯操作缘薛,即將警報(bào)看作是錯(cuò)誤 |
-w | 禁止所有的報(bào)警 |
-share | 此選項(xiàng)將盡量使用動(dòng)態(tài)庫(kù)窍育,所以生成文件比較小,但是需要系統(tǒng)由動(dòng)態(tài)庫(kù) |
-shared | 產(chǎn)生共享對(duì)象文件 |
-static | 使用靜態(tài)鏈接宴胧。此選項(xiàng)將禁止使用動(dòng)態(tài)庫(kù)漱抓。所以編譯出的文件一般都很大,不需要?jiǎng)討B(tài)連接庫(kù) |
-finline-functions,-fnoinline-functions | 啟用/關(guān)閉內(nèi)聯(lián)函數(shù) |
-g | 在編譯結(jié)果中加入調(diào)試信息 |
-ggdb | 加入GDB調(diào)試器能識(shí)別的格式 |
-fPIC | 使用地址無(wú)關(guān)代碼模式進(jìn)行編譯 |
-fPIE | 使用地址無(wú)關(guān)代碼模式編譯可執(zhí)行文件 |
-fomit-frame-pointer | 禁止使用EBP作為函數(shù)幀指針 |
-fno-builtin | 禁止GCC編譯器內(nèi)置函數(shù) |
-fno-stack-protector | 關(guān)閉堆棧保護(hù)功能 |
-ffunction-sections | 將每個(gè)函數(shù)編譯到獨(dú)立的代碼段 |
-fdata-sections | 將全局/靜態(tài)變量編譯到獨(dú)立的數(shù)據(jù)段 |