GCC詳解

一框都、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ù)段
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恕齐,一起剝皮案震驚了整個(gè)濱河市乞娄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌显歧,老刑警劉巖仪或,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異追迟,居然都是意外死亡溶其,警方通過(guò)查閱死者的電腦和手機(jī)骚腥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)敦间,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人束铭,你說(shuō)我怎么就攤上這事廓块。” “怎么了契沫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵带猴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我懈万,道長(zhǎng)拴清,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任会通,我火速辦了婚禮口予,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涕侈。我一直安慰自己沪停,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布裳涛。 她就那樣靜靜地躺著木张,像睡著了一般。 火紅的嫁衣襯著肌膚如雪端三。 梳的紋絲不亂的頭發(fā)上舷礼,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音郊闯,去河邊找鬼妻献。 笑死浮声,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旋奢。 我是一名探鬼主播泳挥,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼至朗!你這毒婦竟也來(lái)了屉符?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锹引,失蹤者是張志新(化名)和其女友劉穎矗钟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體嫌变,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吨艇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腾啥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东涡。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖倘待,靈堂內(nèi)的尸體忽然破棺而出疮跑,到底是詐尸還是另有隱情,我是刑警寧澤凸舵,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布祖娘,位于F島的核電站,受9級(jí)特大地震影響啊奄,放射性物質(zhì)發(fā)生泄漏渐苏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一菇夸、第九天 我趴在偏房一處隱蔽的房頂上張望琼富。 院中可真熱鬧,春花似錦峻仇、人聲如沸公黑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凡蚜。三九已至,卻和暖如春吭从,著一層夾襖步出監(jiān)牢的瞬間朝蜘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工涩金, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谱醇,地道東北人暇仲。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像副渴,于是被迫代替她去往敵國(guó)和親奈附。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容