1、GCC簡介
一般所說的GCC是GUN Compiler Collection的簡稱,除了編譯程序以外,它還含其餘相關工具,因此它能把易於人類使用的高級語言編寫的源代碼構建成計算機可以直接執行的二進制代碼。GCC是Linux平臺下最經常使用的編譯程序,它是Linux平臺編譯器的事實標準。同時,在Linux平臺下的嵌入式開發領域,GCC也是用得最廣泛的一種編譯器。GCC之因此被普遍採用,是由於它能支持各類不一樣的目標體系結構。例如,它既支持基於宿主的開發(簡單講就是要爲某平臺編譯程序,就在該平臺上編譯),也支持交叉編譯(即在A平臺上編譯的程序是供平臺B使用的)。目前,GCC支持的體系結構有四十餘種,常見的有X86系列、Arm、PowerPC等。同時,GCC還能運行在不一樣的操做系統上,如Linux、Solaris、Windows等。
除了上面講的以外,GCC除了支持C語言外,還支持多種其餘語言,例如C++、Ada、Java、Objective-C、FORTRAN、Pascal等。
本系列文章中,咱們不只介紹GCC的基本功能,還涉及到一些諸如優化之類的高級功能。另外,咱們還考察GCC的一些映像操做工具,如size和objcopy等,這將在後續的文章中加以介紹。
2、程序的編譯過程
對於GUN編譯器來講,程序的編譯要經歷預處理、編譯、彙編、鏈接四個階段,以下圖所示:
從功能上分,預處理、編譯、彙編是三個不一樣的階段,但GCC的實際操做上,它能夠把這三個步驟合併爲一個步驟來執行。下面咱們以C語言爲例來談一下不一樣階段的輸入和輸出狀況。
在預處理階段,輸入的是C語言的源文件,一般爲*.c。它們一般帶有.h之類頭文件的包含文件。這個階段主要處理源文件中的#ifdef、 #include和#define命令。該階段會生成一箇中間文件*.i,但實際工做中一般不用專門生成這種文件,由於基本上用不到;若非要生成這種文件不可,能夠利用下面的示例命令:
在編譯階段,輸入的是中間文件*.i,編譯後生成彙編語言文件*.s 。這個階段對應的GCC命令以下所示:
在彙編階段,將輸入的彙編文件*.s轉換成機器語言*.o。這個階段對應的GCC命令以下所示:
最後,在鏈接階段將輸入的機器代碼文件*.s(與其它的機器代碼文件和庫文件)聚集成一個可執行的二進制代碼文件。這一步驟,能夠利用下面的示例命令完成:
上面介紹了GCC編譯過程的四個階段以及相應的命令。下面咱們進一步介紹經常使用的GCC的模式。
3、GCC經常使用模式
這裏介紹GCC追經常使用的兩種模式:編譯模式和編譯鏈接模式。下面以一個例子來講明各類模式的使用方法。爲簡單起見,假設咱們所有的源代碼都在一個文件test.c中,要想把這個源文件直接編譯成可執行程序,可使用如下命令:
這裏test.c是源文件,生成的可執行代碼存放在一個名爲test 的文件中(該文件是機器代碼而且可執行)。-o 是生成可執行文件的輸出選項。若是咱們只想讓源文件生成目標文件(給文件雖然也是機器代碼但不可執行),可使用標記-c ,詳細命令以下所示:
默認狀況下,生成的目標文件被命名爲test.o,但咱們也能夠爲輸出文件指定名稱,以下所示:
$ GCC -c test.c -o
mytest.o
|
上面這條命令將編譯後的目標文件命名爲mytest.o,而不是默認的test.o。
迄今爲止,咱們談論的程序僅涉及到一個源文件;現實中,一個程序的源代碼一般包含在多個源文件之中,這該怎麼辦?不要緊,即便這樣,用GCC處理起來也並不複雜,見下例:
$ GCC first.c second.c third.c
-o test
|
該命令將同時編譯三個源文件,即first.c、second.c和 third.c,而後將它們鏈接成一個可執行程序,名爲test。
須要注意的是,要生成可執行程序時,一個程序不管有有一個源文件仍是多個源文件,全部被編譯和鏈接的源文件中必須有且僅有一個main函數,由於main函數是該程序的入口點(換句話說,當系統調用該程序時,首先將控制權授予程序的main函數)。但若是僅僅是把源文件編譯成目標文件的時候,由於不會進行鏈接,因此main函數不是必需的。
4、經常使用選項
許多狀況下,頭文件和源文件會單獨存放在不一樣的目錄中。例如,假設存放源文件的子目錄名爲./src,而包含文件則放在層次的其餘目錄下,如./inc。當咱們在./src 目錄下進行編譯工做時,如何告訴GCC到×××頭文件呢?方法以下所示:
$ gcc test.c –I
../inc -o test
|
上面的命令告訴GCC包含文件存放在./inc 目錄下,在當前目錄的上一級。若是在編譯時須要的包含文件存放在多個目錄下,可使用多個-I 來指定各個目錄:
$ gcc test.c –I
../inc –I
../../inc2 -o test
|
這裏指出了另外一個包含子目錄inc2,較以前目錄它還要在再上兩級才能找到。
另外,咱們還能夠在編譯命令行中定義符號常量。爲此,咱們能夠簡單的在命令行中使用-D選項便可,以下例所示:
$ gcc -DTEST_CONFIGURATION test.c -o test
|
上面的命令與在源文件中加入下列命令是等效的:
#define TEST_CONFIGURATION
|
在編譯命令行中定義符號常量的好處是,沒必要修改源文件就能改變由符號常量控制的行爲。
5、警告功能
當GCC在編譯過程當中檢查出錯誤的話,它就會停止編譯;但檢測到警告時卻能繼續編譯生成可執行程序,由於警告只是針對程序結構的診斷信息,它不能說明程序必定有錯誤,而是存在風險,或者可能存在錯誤。雖然GCC提供了很是豐富的警告,但前提是你已經啓用了它們,不然它不會報告這些檢測到的警告。
在衆多的警告選項之中,最經常使用的就是-Wall選項。該選項能發現程序中一系列的常見錯誤警告,該選項用法舉例以下:
$ gcc -Wall test.c -o test
|
該選項至關於同時使用了下列全部的選項:
◆unused-function:遇到僅聲明過但還沒有定義的靜態函數時發出警告。
◆unused-label:遇到聲明過但不使用的標號的警告。
◆unused-parameter:從未用過的函數參數的警告。
◆unused-variable:在本地聲明但從未用過的變量的警告。
◆unused-value:僅計算但從未用過的值得警告。
◆Format:檢查對printf和scanf等函數的調用,確認各個參數類型和格式串中的一致。
◆implicit-int:警告沒有規定類型的聲明。
◆implicit-function-:在函數在未經聲明就使用時給予警告。
◆char-subscripts:警告把char類型做爲數組下標。這是常見錯誤,程序員常常忘記在某些機器上char有符號。
◆missing-braces:聚合初始化兩邊缺乏大括號。
◆Parentheses:在某些狀況下若是忽略了括號,編譯器就發出警告。
◆return-type:若是函數定義了返回類型,而默認類型是int型,編譯器就發出警告。同時警告那些不帶返回值的 return語句,若是他們所屬的函數並不是void類型。
◆sequence-point:出現可疑的代碼元素時,發出報警。
◆Switch:若是某條switch語句的參數屬於枚舉類型,可是沒有對應的case語句使用枚舉元素,編譯器就發出警告(在switch語句中使用default分支可以防止這個警告)。超出枚舉範圍的case語句一樣會致使這個警告。
◆strict-aliasing:對變量別名進行最嚴格的檢查。
◆unknown-pragmas:使用了不容許的#pragma。
◆Uninitialized:在初始化以前就使用自動變量。
須要注意的是,各警告選項既然能使之生效,固然也能使之關閉。好比假設咱們想要使用-Wall來啓用個選項,同時又要關閉unused警告,利益經過下面的命令來達到目的:
$ gcc -Wall -Wno-unused test.c -o test
|
下面是使用-Wall選項的時候沒有生效的一些警告項:
◆cast-align:一旦某個指針類型強制轉換時,會致使目標所需的地址對齊邊界擴展,編譯器就發出警告。例如,某些機器上只能在2或4字節邊界上訪問整數,若是在這種機型上把char *強制轉換成int *類型, 編譯器就發出警告。
◆sign-compare:將有符號類型和無符號類型數據進行比較時發出警告。
◆missing-prototypes :若是沒有預先聲明函數原形就定義了全局函數,編譯器就發出警告。即便函數定義自身提供了函數原形也會產生這個警告。這樣作的目的是檢查沒有在頭文件中聲明的全局函數。
◆Packed:當結構體帶有packed屬性但實際並無出現緊縮式給出警告。
◆Padded:若是結構體經過充填進行對齊則給出警告。
◆unreachable-code:若是發現從未執行的代碼時給出警告。
◆Inline:若是某函數不能內嵌(inline),不管是聲明爲inline或者是指定了-finline-functions 選項,編譯器都將發出警告。
◆disabled-optimization:當須要太長時間或過多資源而致使不能完成某項優化時給出警告。
上面是使用-Wall選項時沒有生效,但又比較經常使用的一些警告選項。本文中要介紹的最後一個經常使用警告選項是-Werror。使用該選項後,GCC發現可疑之處時不會簡單的發出警告就算完事,而是將警告做爲一個錯誤而中斷編譯過程。該選項在但願獲得高質量代碼時很是有用。
6、小結
本文介紹了GCC的基本編譯過程和編譯模式,並詳細闡述了GCC的一些經常使用選項以及警告功能。這些是在利用GCC進行應用編程時最基本也最經常使用的一些內容,咱們會在後續文章中繼續介紹GCC的調試和優化技術。