本節書摘來異步社群《嵌入式 linux c 語言應用程式設計(修訂版)》一書中的第2章,第2.3節,作者:華清遠見嵌入式教育訓練中心,孫瓊,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
嵌入式 linux c 語言應用程式設計(修訂版)
作為自由軟體的旗艦項目,richard stallman在十多年前剛開始寫作gcc的時候,還隻是僅僅把它當作一個c程式語言的編譯器,gcc的意思也隻是gnu c compiler而已。
經過了這麼多年的發展,gcc已經不僅僅能支援c語言,它現在還支援ada語言、c++語言、java語言、objective c語言、pascal語言、cobol語言,并支援函數式程式設計和邏輯程式設計的mercury語言等。而gcc也不再單隻gnu c語言編譯器的意思了,而是變成了gnu編譯器家族了。
正如前文中所述,gcc的編譯流程分為了4個步驟,分别為。
預處理(pre-processing)。
編譯(compiling)。
彙編(assembling)。
連結(linking)。
編譯器通過程式的擴充名可分辨編寫原始程式碼所用的語言,由于不同的程式所需要執行編譯的步驟是不同的,是以gcc根據不同的字尾名對它們進行分别處理,表2.6指出了不同字尾名的處理方式。
gcc使用的基本文法為:
/hello.h/
typedef unsigned long val32_t;
/hello.c/
int main()
{
}<code>`</code>
1.預處理階段
gcc的選項“-e”可以使編譯器在預處理結束時就停止編譯,選項“-o”是指定gcc輸出的結果,其指令格式為如下所示。
[root@localhost gcc]# gcc –e –o hello.i hello.c<code>`</code>
在此處,選項‘-o’是指目标檔案,由2.6表可知,‘.i’檔案為已經過預處理的c原始程式。以下列出了hello.i檔案的部分内容。
[root@localhost gcc]# gcc –s –o hello.s hello.i<code>`</code>
以下列出了hello.s的内容,可見gcc已經将其轉化為彙編了,感興趣的讀者可以分析一下這一行簡單的c語言小程式用彙編代碼是如何實作的。
[root@localhost gcc]# gcc –c hello.s –o hello.o<code>`</code>
4.連結階段
在成功編譯之後,就進入了連結階段。在這裡涉及一個重要的概念:函數庫。
在這個程式中并沒有定義“printf”的函數實作,在預編譯中包含進的“stdio.h”中也隻有該函數的聲明,而沒有定義函數的實作,那麼,是在哪裡實作“printf”函數的呢?
最後的答案是:系統把這些函數實作都已經被放入名為libc.so.6的庫檔案中去了,在沒有特别指定時,gcc會到系統預設的搜尋路徑“/usr/lib”下進行查找,也就是連結到libc.so.6庫函數中去,這樣就能實作函數“printf”了,而這也就是連結的作用。
完成了連結之後,gcc就可以生成可執行檔案,其指令如下所示。
[root@localhost gcc]# ./hello
hello, embedded world 5<code>`</code>
本節主要講解gcc的警告提示功能。gcc包含完整的出錯檢查和警告提示功能,它們可以幫助linux程式員寫出更加專業和優美的代碼。
讀者千萬不能小瞧這些警告資訊,在很多情況下,含有警告資訊的代碼往往會有意想不到的運作結果。
首先讀者可以先看一下以下這段代碼:
[root@ft charpter2]# gcc -wall wrong.c -o wrong
wrong.c:4: warning: return type of 'main' is not 'int'
wrong.c: in function 'main':
wrong.c:5: warning: unused variable 'tmp'<code>`</code>
可以看出,使用‘-wall’選項找出了未使用的變量tmp以及傳回值的問題,但沒有找出無效資料類型的錯誤。
2.非wall類警告提示
非wall類的警告提示中最為常用的有以下兩種:“-ansi”和“-pedantic”。
(1)“-ansi”
該選項強制gcc生成标準文法所要求的告警資訊,盡管這還并不能保證所有沒有警告的程式都是符合ansi c标準的。使用該選項的運作結果如下所示:
[root@ft charpter2]# gcc -pedantic wrong.c -o wrong
wrong.c:5: warning: iso c90 does not support 'long long'
wrong.c:4: warning: return type of 'main' is not 'int'<code>`</code>
可以看出,使用該選項檢視出了“long long”這個無效資料類型的錯誤。
1.linux函數庫介紹
函數庫可以看做是事先編寫的函數集合,它可以與主函數分離,進而增加程式開發的複用性。linux中函數庫可以有3種使用的形式:靜态、共享和動态。
靜态庫的代碼在編譯時就已連接配接到開發人員開發的應用程式中,而共享庫隻是在程式開始運作時才載入。
動态庫也是在程式運作時載入,但與共享庫不同的是,動态庫使用的庫函數不是在程式運作使開始載入,而是在程式中的語句需要使用該函數時才載入。動态庫可以在程式運作期間釋放動态庫所占用的記憶體,騰出空間供其他程式使用。
由于共享庫和動态庫并沒有在程式中包括庫函數的内容,隻是包含了對庫函數的引用,是以代碼的規模比較小。
系統中可用的庫都存放在/usr/lib和/lib目錄中。庫檔案名由字首lib和庫名以及字尾組成。根據庫的類型不同,字尾名也不一樣。
stnd001注意 共享庫和動态庫的字尾名由.so和版本号組成。
靜态庫的字尾名為.a。
如:數學共享庫的庫名為libm.so.5,這裡的辨別字元為m,版本号為5,libm.a則是靜态數學庫。在linux系統中系統所用的庫都存放在/usr/lib和/lib目錄中。
2.相關路徑選項
由于庫檔案的通常路徑不是在系統預設的路徑下,是以,首先要使用調用路徑選項來指定相關的庫檔案位置,這裡首先講解兩個常用選項的使用方法。
(1)“-i dir”
在gcc中使用頭檔案在預設情況下是在主程式中所設定的路徑,那麼如果想要改變該路徑,使用者則可以使用“-i”選項。“-i dir”選項可以在頭檔案的搜尋路徑清單中添加dir目錄。這時,gcc就會到相應的位置查找對應的目錄。
比如在“/root/workplace/gcc”下有兩個檔案:
[root@localhost gcc] gcc hello.c –i /root/workplace/gcc/ -o hello<code>`</code>
這樣,gcc就能夠執行出正确結果。
0115小技巧 在include語句中,“<>”表示在标準路徑中搜尋頭檔案,在linux中預設為“/usr/include”。故在上例中,可把hello1.c的“#include”改為“#include "my.h"”,這樣就不需要加上“-i”選項了。
(2)“-l dir”
選項“-l dir”的功能與“-i dir”類似,其差別就在于“-l”選項是用于指明庫檔案的路徑。例如有程式hello_sq.c需要用到目錄“/root/workplace/gcc/lib”下的一個動态庫libsunq.so,則隻需鍵入如下指令即可。
[root@localhost gcc] gcc -o dynamic -l /root/lq/testc/lib/dynamic.o -lmydynamic<code>`</code>
那麼,若系統中同時存在檔案名相同的靜态庫檔案和動态庫檔案時,該連結選項究竟會調用靜态庫檔案還是動态庫檔案呢?
經測試後可以發現,系統調用的是動态庫檔案,這是由于linux系統中預設的是采用動态連結的方式。這樣,若使用者要調用含有同名動态庫檔案的靜态庫檔案,則在“-l”後需要顯示地寫出包含字尾名的檔案名,如:要調用libm.a庫檔案時就需寫作“-llibm.a”。
gcc可以對代碼進行優化,它通過編譯選項-on來控制優化代碼的生成,其中n是一個代表優化級别的整數。對于不同版本的gcc來講,n的取值範圍及其對應的優化效果可能并不完全相同,比較典型的範圍是從0變化到2或3。
不同的優化級别對應不同的優化處理工作,如使用優化選項-o主要進行線程跳轉(thread jump)和延遲退棧(deferred stack pops)兩種優化。
使用優化選項-o2除了完成所有-o1級别的優化之外,同時還要進行一些額外的調整工作,如處理器指令排程等;選項-o3則還包括循環展開和其他一些與處理器特性相關的優化工作。
雖然優化選項可以加速代碼的運作速度,但對于調試而言将是一個很大的挑戰。因為代碼在經過優化之後,原先在源程式中聲明和使用的變量很可能不再使用,控制流也可能會突然跳轉到意外的地方,循環語句也有可能因為循環展開而變得到處都有,所有這些都将使調試工作異常堅難。
建議在調試的時候最好不使用任何優化選項,隻有當程式在最終發行的時候才考慮對其進行優化。