by falcon <zhangjinw@gmail.com>
平時(shí)在Linux下寫(xiě)代碼,直接用"gcc -o out
in.c"就把代碼編譯好了,但是這后面到底做了什么事情呢?如果學(xué)習(xí)過(guò)編譯原理則不難理解,一般高級(jí)語(yǔ)言程序編譯的過(guò)程莫過(guò)于:預(yù)處理、編譯、匯編、鏈
接。gcc在后臺(tái)實(shí)際上也經(jīng)歷了這幾個(gè)過(guò)程,我們可以通過(guò)-v參數(shù)查看它的編譯細(xì)節(jié),如果想看某個(gè)具體的編譯過(guò)程,則可以分別使用-E,-S,-c和-
O,對(duì)應(yīng)的后臺(tái)工具則分別為cpp,cc1,as,ld。下面我們將逐步分析這幾個(gè)過(guò)程以及相關(guān)的內(nèi)容,諸如語(yǔ)法檢查、代碼調(diào)試、匯編語(yǔ)言等。
1、預(yù)處理
開(kāi)篇簡(jiǎn)述:預(yù)處理是C語(yǔ)言程序從源代碼變成可執(zhí)行程序的第一步,主要是C語(yǔ)言編譯器對(duì)各種預(yù)處理命令進(jìn)行處理,包括頭文件的包含、宏定義的擴(kuò)展、條件編譯的選擇等。
以前沒(méi)怎么“深入”預(yù)處理,腦子對(duì)這些東西總是很模糊,只記得在編譯的基本過(guò)程(詞法分析、語(yǔ)法分析)之前還需要對(duì)源代碼中的宏定義、文件包含、條件編譯
等命令進(jìn)行處理。這三類的指令很常見(jiàn),主要有#define, #include和#ifdef ...
#endif,要特別地注意它們的用法。(更多預(yù)處理的指令請(qǐng)查閱相關(guān)資料)
#define除了可以獨(dú)立使用以便靈活設(shè)置一些參數(shù)外,還常常和#ifdef ...
#endif結(jié)合使用,以便靈活地控制代碼塊的編譯與否,也可以用來(lái)避免同一個(gè)頭文件的多次包含。關(guān)于#include貌似比較簡(jiǎn)單,通過(guò)man找到某個(gè)
函數(shù)的頭文件,copy進(jìn)去,加上<>就okay。這里雖然只關(guān)心一些技巧,不過(guò)預(yù)處理還是蘊(yùn)含著很多潛在的陷阱(可參考<C
Traps & Pitfalls>),我們也需要注意的。下面僅介紹和預(yù)處理相關(guān)的幾個(gè)簡(jiǎn)單內(nèi)容。
打印出預(yù)處理之后的結(jié)果:gcc -E hello.c
這樣我們就可以看到源代碼中的各種預(yù)處理命令是如何被解釋的,從而方便理解和查錯(cuò)。
實(shí)際上gcc在這里是調(diào)用了cpp的(雖然我們通過(guò)gcc的-v僅看到cc1),cpp即The C Preprocessor,主要用來(lái)預(yù)處理宏定義、文件包含、條件編譯等。下面介紹它的一個(gè)比較重要的選項(xiàng)-D。
在命令行定義宏:gcc -Dmacro hello.c
這個(gè)等同于在文件的開(kāi)頭定義宏,即#define maco,但是在命令行定義更靈活。例如,在源代碼中有這些語(yǔ)句。
#ifdef DEBUG
printf("this code is for debugging\n");
#endif
如果編譯時(shí)加上-DDEBUG選項(xiàng),那么編譯器就會(huì)把printf所在的行編譯進(jìn)目標(biāo)代碼,從而方便地跟蹤該位置的某些程序狀態(tài)。這樣-DDEBUG就可以當(dāng)作一個(gè)調(diào)試開(kāi)關(guān),編譯時(shí)加上它就可以用來(lái)打印調(diào)試信息,發(fā)布時(shí)則可以通過(guò)去掉該編譯選項(xiàng)把調(diào)試信息去掉。
本節(jié)參考資料:
[1] C語(yǔ)言教程第九章:預(yù)處理
http://www.bc-cn.net/Article/kfyy/cyy/jc/200409/9.html
[2] 更多
http://www.hemee.com/kfyy/c/6626.html
http://www.91linux.com/html/article/program/cpp/20071203/8745.html
http://www.janker.org/bbs/programmer/2006-10-13/327.html
2、編譯(翻譯)
開(kāi)篇簡(jiǎn)要:編譯之前,C語(yǔ)言編譯器會(huì)進(jìn)行詞法分析、語(yǔ)法分析(-fsyntax-only),接著會(huì)把源代碼翻譯成中間語(yǔ)言,即匯編語(yǔ)言。如果想看到這個(gè)
中間結(jié)果,可以用-S選項(xiàng)。需要提到的是,諸如shell等解釋語(yǔ)言也會(huì)經(jīng)歷一個(gè)詞法分析和語(yǔ)法分析的階段,不過(guò)之后并不會(huì)進(jìn)行“翻譯”,而是“解釋”,
邊解釋邊執(zhí)行。
把源代碼翻譯成匯編語(yǔ)言,實(shí)際上是編譯的整個(gè)過(guò)程中的第一個(gè)階段,之后的階段和匯編語(yǔ)言的開(kāi)發(fā)過(guò)程沒(méi)有什么區(qū)別。這個(gè)階段涉及到對(duì)源代碼的詞法分析、語(yǔ)法檢查(通過(guò)-std指定遵循哪個(gè)標(biāo)準(zhǔn)),并根據(jù)優(yōu)化(-O)要求進(jìn)行翻譯成匯編語(yǔ)言的動(dòng)作。
如果僅僅希望進(jìn)行語(yǔ)法檢查,可以用-fsyntax-only選項(xiàng);而為了使代碼有比較好的移植性,避免使用gcc的一些特性,可以結(jié)合-std和-
pedantic(或者-pedantic-erros)選項(xiàng)讓源代碼遵循某個(gè)C語(yǔ)言標(biāo)準(zhǔn)的語(yǔ)法。這里演示一個(gè)簡(jiǎn)單的例子。
Quote: |
$ cat hello.c #include <stdio.h> int main() { printf("hello, world\n") return 0; } $ gcc -fsyntax-only hello.c hello.c: In function ‘main’: hello.c:5: error: expected ‘;’ before ‘return’ $ vim hello.c $ cat hello.c #include <stdio.h> int main() { printf("hello, world\n"); int i; return 0; } $ gcc -std=c89 -pedantic-errors hello.c #默認(rèn)情況下,gcc是允許在程序中間聲明變量的,但是turboc就不支持 hello.c: In function ‘main’: hello.c:5: error: ISO C90 forbids mixed declarations and code
|
語(yǔ)法錯(cuò)誤是程序開(kāi)發(fā)過(guò)程中難以避免的錯(cuò)誤(人的大腦在很多條件下都容易開(kāi)小差),不過(guò)編譯器往往能夠通過(guò)語(yǔ)法檢查快速發(fā)現(xiàn)這些錯(cuò)誤,并準(zhǔn)確地告訴你語(yǔ)法錯(cuò)
誤的大概位置。因此,作為開(kāi)發(fā)人員,要做的事情不是“恐慌”(不知所措),而是認(rèn)真閱讀編譯器的提示,根據(jù)平時(shí)積累的經(jīng)驗(yàn)(最好在大腦中存一份常見(jiàn)語(yǔ)法錯(cuò)
誤索引,很多資料都提供了常見(jiàn)語(yǔ)法錯(cuò)誤列表,如<C
Traps&Pitfalls>和最后面的參考資料[12]也列出了很多常見(jiàn)問(wèn)題)和編輯器提供的語(yǔ)法檢查功能(語(yǔ)法加亮、括號(hào)匹配提示
等)快速定位語(yǔ)法出錯(cuò)的位置并進(jìn)行修改。
語(yǔ)法檢查之后就是翻譯動(dòng)作,gcc提供了一個(gè)優(yōu)化選項(xiàng)-O,以便根據(jù)不同的運(yùn)行平臺(tái)和用戶要求產(chǎn)生經(jīng)過(guò)優(yōu)化的匯編代碼。例如,
Quote: |
$ gcc -o hello hello.c #采用默認(rèn)選項(xiàng),不優(yōu)化 $ gcc -O2 -o hello2 hello.c #優(yōu)化等次是2 $ gcc -Os -o hellos hello.c #優(yōu)化目標(biāo)代碼的大小 $ ls -S hello hello2 hellos #可以看到,hellos比較小,hello2比較大 hello2 hello hellos $ time ./hello hello, world
real 0m0.001s user 0m0.000s sys 0m0.000s $ time ./hello2 #可能是代碼比較少的緣故,執(zhí)行效率看上去不是很明顯 hello, world
real 0m0.001s user 0m0.000s sys 0m0.000s
$ time ./hellos #雖然目標(biāo)代碼小了,但是執(zhí)行效率慢了些 hello, world
real 0m0.002s user 0m0.000s sys 0m0.000s
|
根據(jù)上面的簡(jiǎn)單演示,可以看出gcc有很多不同的優(yōu)化選項(xiàng),主要看用戶的需求了,目標(biāo)代碼的大小和效率之間貌似存在一個(gè)“糾纏”,需要開(kāi)發(fā)人員自己權(quán)衡。
下面我們通過(guò)-S選項(xiàng)來(lái)看看編譯出來(lái)的中間結(jié)果,匯編語(yǔ)言,還是以之前那個(gè)hello.c為例。
Quote: |
$ gcc -S hello.c #默認(rèn)輸出是hello.s,可自己指定,輸出到屏幕-o -,輸出到其他文件-o file $ cat hello.s cat hello.s .file "hello.c" .section .rodata .LC0: .string "hello, world" .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $4, %esp movl $.LC0, (%esp) call puts movl $0, %eax addl $4, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret .size main, .-main .ident "GCC: (GNU) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)" .section .note.GNU-stack,"",@progbits
|
不知道看出來(lái)沒(méi)?和我們?cè)谡n堂里學(xué)的intel的匯編語(yǔ)法不太一樣,這里用的是AT&T語(yǔ)法格式。如果之前沒(méi)接觸過(guò)AT&T的,可以看看
參考資料[2]。如果想學(xué)習(xí)Linux下的匯編語(yǔ)言開(kāi)發(fā),從下一節(jié)開(kāi)始哦,下一節(jié)開(kāi)始的所有章節(jié)基本上覆蓋了Linux下匯編語(yǔ)言開(kāi)發(fā)的一般過(guò)程,不過(guò)這
里不介紹匯編語(yǔ)言語(yǔ)法。
這里需要補(bǔ)充的是,在寫(xiě)C語(yǔ)言代碼時(shí),如果能夠?qū)幾g器比較熟悉(工作原理和一些細(xì)節(jié))的話,可能會(huì)很有幫助。包括這里的優(yōu)化選項(xiàng)(有些優(yōu)化選項(xiàng)可能在匯
編時(shí)采用)和可能的優(yōu)化措施,例如字節(jié)對(duì)齊(可以看看這本書(shū)"Linux_Assembly_Language_Programming"的第六小節(jié))、
條件分支語(yǔ)句裁減(刪除一些明顯分支)等。
本節(jié)參考資料
[1] Guide to Assembly Language Programming in Linux(pdf教程,社區(qū)有下載)
http://oss.lzu.edu.cn/modules/wfdownloads/singlefile.php?cid=5&lid=94
[2] Linux匯編語(yǔ)言開(kāi)發(fā)指南(在線):
http://www.ibm.com/developerworks/cn/linux/l-assembly/index.html
[3] PowerPC 匯編
http://www.ibm.com/developerworks/cn/linux/hardware/ppc/assembly/index.html
[4] 用于 Power 體系結(jié)構(gòu)的匯編語(yǔ)言
http://www.ibm.com/developerworks/cn/linux/l-powasm1.html
[5] Linux Assembly HOWTO
http://mirror.lzu.edu.cn/tldp/HOWTO/Assembly-HOWTO/
[6] Linux 中 x86 的內(nèi)聯(lián)匯編
http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index.html
[7] Linux Assembly Language Programming
http://mirror.lzu.edu.cn/doc/incoming/ebooks/linux-unix/Linux_EN_Original_Books