用慣了圖形化的IDE,對編譯和鏈接部分的東西知道的病不多,這裡補習一下。
1. 首先編譯和鏈接是兩個東西,雖然在IDE上按一個鍵結果就出來了,這是因為IDE在後台自己做了不少的事情。
2. 常見的編譯器有gcc/g++,前者是給c用的,後者是給c++用的,兩者的編譯操作差別不大;常見的連接器有ld。
3. gcc會把鏈接的事情也做掉(除非加參數),所以對ld的了解更少...
下面以gcc和ld為例說明c語言(c++也差不多,只是要換編譯器為g++)的編譯和鏈接。
首先看下面的一個最簡單的例子:
#include在IDE下,比如這邊使用的Xcode,只要Cmd+R,就可以直接得到運行結果了:int main(int argc, const char * argv[]) { printf("Hello, World!\n"); return 0; }
雖然不明白IDE打的做了什麼,但是覺得很厲害的樣子。
對大部分只關注代碼實現的人來說,能夠正確使用就OK了,但是如果希望進一步了解代碼的編譯和鏈接,則需要關掉IDE,打開shell,來探索gcc跟ld了。
gcc的基本格式如下:
gcc [option] files
可以看到也能得到與Xcode執行時一樣的結果。
連ld都省了。
到底gcc做了些什麼,可以參考Journey of a C Program to Linux Executable in 4 Stages這篇文章。
接下來需要介紹一些常用的gcc選項,上面的例子中,雖然我們沒有使用編譯選項,但是gcc還是會根據默認的選項值來進行編譯。
當我們了解了gcc的編譯選項,就可以更好的控制編譯的過程。
下面就是一些常用的選項:
-o file:給輸出文件命名,在上例中,我們沒有指定名稱,所以使用了默認的a.out,如果你指定了名稱,比如下面這樣,就會有不同的結果:
-Wall:這個是用來顯示編譯時的告警的。比如在我們的代碼中添加一個聲明了但未使用的變量:
如果不加-Wall參數,就不會報錯。
開啟-Wall可以讓上報一些我們自己沒有發現的問題,值得推薦。
-Werror:這個選項的意思是把編譯中的warning當作錯誤。上面的例子中,雖然-Wall之後編譯時會有提示,但是最終還是會生成a.out,即整個編譯過程是會運行完成的,而如果又加了-Werror,在編譯過程中就報錯並終止了:
-Wall -Werror一起使用,能夠不放過任何的編譯問題。
-S:用來生成對應的匯編代碼:
查看匯編代碼:
可以看到生成的匯編代碼是AT&T格式的,但是如果只知道Intel的匯編格式呢?可以再加上-masm=intel這個參數:
這樣生成的就是intel格式的匯編代碼了:
現在需要用到匯編的機會不多,除非是性能要求需要優化代碼,或者debug的時候才會去用吧。
-E:加入了-E選項後,gcc並不進行編譯,而是進行預編譯,就是把宏進行替換、頭文件展開等。在原來的代碼上加一個宏,下面是執行-E後的情況:
-c:指定這個選項後,gcc只負責編譯,但是不會進行鏈接:
生成的.o文件需要經過鏈接才能成為可執行的文件,這裡就需要用到ld了,先不介紹。
-save-temps:使用這個命令可以保留在gcc編譯和鏈接過程中的中間文件:
這些中間文件對於了解gcc的工作過程很有幫助。很多文件在之前已經介紹過了,另外的,main.i就是預處理後的代碼,main.bc是什麼還不清楚...
-l和-L:這兩個是用來指定庫的,前者指定庫名,後者指定庫的路徑,可以參考http://blog.csdn.net/jiangwei0512/article/details/51559030的說明。
-v:加上這個參數可以在編譯時看到不少額外的信息:
-D:後接宏來控制代碼的走向。
#include上面的代碼中,ERROR宏控制的代碼是錯的,如果使用-DERROR,則在編譯的時候就會包含aaaa這樣的代碼,導致編譯錯誤:#define MAX 255 int main(int argc, const char * argv[]) { #ifdef ERROR aaaa; #endif int i = MAX; printf("Hello, World!\n"); return 0; }
而不加-D,就不會報錯。
這裡需要注意下-D和宏之間沒有空格。
編譯參數介紹到這裡,下面需要介紹鏈接器ld。
ld和gcc使用的方式差不多,也是ld +選項 +文件。
這裡的文件需要時.o文件,即gcc -c編譯後的文件。
ld最重要的作用當然是生成可執行的文件。但是還有一個非常重要的作用,就是找到我們的代碼中並沒有定義的那些函數,然後在當前平台上找到相應的實現。
比如我們的代碼中有一個printf,它是c標准庫中實現的,所以鏈接時就需要找到相應的庫,並獲取到printf的實現代碼。
下面是鏈接的操作過程:
首先,當然要生成.o文件,參見前面講-c時的圖片;
然後,像使用gcc一樣,什麼參數也不加來執行下:
發現有錯誤,並沒有生成可執行文件。
前兩個warning是針對mac的,通過加參數-macosx_version_min就可以解決。
後一個錯誤是因為沒有找到_printf的實現,這是因為我們沒有指定庫。
下面是修改後的執行命令:
可以看到能夠生成a.out了,但是還是有報之前的warning,原因是因為沒有指定系統架構,修改成下面的形式:
就可以得到我們需要的結果了。
下面也介紹下ld常用的選項(像-macox_verson_min這種就不說了):
-arch:用來指定系統架構,比如Intel 32位機是i386,Intel 64位機是x86_64。
-lx:對應上面的命令就是-lc,表示的是尋找libc.a或者libc.dylib這樣的庫,因為printf就在這些庫中實現的。
-e:用來指定入口,默認是_main,對應到代碼中就是main,所以我們不需要顯式得指定,當然指定也沒有關系:
以上就是對gcc和ld的介紹,它們可以帶的選項還有很多,要了解具體所有選線,可以在shell下使用man gcc/ld來查看。
PS:發現一個問題,在mac中gcc被指向了clang編譯器,所以上面的gcc操作實際上都是clang的操作......不過基本的功能都沒有變,參數在gcc中也能夠正常使用,所以基本上也算是gcc的說明吧......