天天看點

《嵌入式 Linux C 語言應用程式設計(修訂版)》——2.3 嵌入式Linux編譯器GCC的使用

本節書摘來自異步社群《嵌入式 linux c 語言應用程式設計(修訂版)》一書中的第2章,第2.3節,作者 華清遠見嵌入式教育訓練中心,孫瓊,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

作為自由軟體的旗艦項目,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指出了不同字尾名的處理方式。

《嵌入式 Linux C 語言應用程式設計(修訂版)》——2.3 嵌入式Linux編譯器GCC的使用

gcc使用的基本文法為:

這裡的option是gcc使用時的一些選項,通過指定不同的選項gcc可以實作其強大的功能。這裡的filename則是gcc要編譯的檔案,gcc會根據使用者所指定的編譯選項以及所識别的檔案字尾名來對編譯檔案進行相應的處理。

本節從編譯流程的角度講解gcc的常見使用方法。

首先,這裡有一段簡單的c語言程式,該程式由兩個檔案組成,其中“hello.h”為頭檔案,在“hello.c”中包含了“hello.h”,其源檔案如下所示。

1.預處理階段

gcc的選項“-e”可以使編譯器在預處理結束時就停止編譯,選項“-o”是指定gcc輸出的結果,其指令格式為如下所示。

表2.6指出字尾名為“.i”的檔案是經過預處理的c原始程式。要注意,“hello.h”檔案是不能進行編譯的,是以,使編譯器在預處理後停止的指令如下所示。

在此處,選項‘-o’是指目标檔案,由2.6表可知,‘.i’檔案為已經過預處理的c原始程式。以下列出了hello.i檔案的部分内容。

由此可見,gcc确實進行了預處理,它把“hello.h”的内容插入到hello.i檔案中了。

2.編譯階段

編譯器在預處理結束之後,gcc首先要檢查代碼的規範性、是否有文法錯誤等,以确定代碼的實際要做的工作,在檢查無誤後,就開始把代碼翻譯成彙編語言,gcc的選項“-s”能使編譯器在進行完彙編之前就停止。由表2.6可知,“.s”是彙編語言原始程式,是以,此處的目标檔案就可設為“.s”類型。

以下列出了hello.s的内容,可見gcc已經将其轉化為彙編了,感興趣的讀者可以分析一下這一行簡單的c語言小程式用彙編代碼是如何實作的。

可以看到,這一小段c語言的程式在彙編中已經複雜很多了,這也是c語言作為中級語言的優勢所在。

3.彙編階段

彙編階段是把編譯階段生成的“.s”檔案生成目标檔案,讀者在此使用選項“-c”就可看到彙編代碼已轉化為“.o”的二進制目标代碼了。如下所示。

4.連結階段

在成功編譯之後,就進入了連結階段。在這裡涉及一個重要的概念:函數庫。

在這個程式中并沒有定義“printf”的函數實作,在預編譯中包含進的“stdio.h”中也隻有該函數的聲明,而沒有定義函數的實作,那麼,是在哪裡實作“printf”函數的呢?

最後的答案是:系統把這些函數實作都已經被放入名為libc.so.6的庫檔案中去了,在沒有特别指定時,gcc會到系統預設的搜尋路徑“/usr/lib”下進行查找,也就是連結到libc.so.6庫函數中去,這樣就能實作函數“printf”了,而這也就是連結的作用。

完成了連結之後,gcc就可以生成可執行檔案,其指令如下所示。

運作該可執行檔案,出現正确的結果。

本節主要講解gcc的警告提示功能。gcc包含完整的出錯檢查和警告提示功能,它們可以幫助linux程式員寫出更加專業和優美的代碼。

讀者千萬不能小瞧這些警告資訊,在很多情況下,含有警告資訊的代碼往往會有意想不到的運作結果。

首先讀者可以先看一下以下這段代碼:

雖然這段代碼運作的結果是正确的,但還有以下問題。

main函數的傳回值被聲明為void,但實際上應該是int。

使用了gnu文法擴充,即使用long long來聲明64位整數,不符合ansi/iso c語言标準。

main函數在終止前沒有調用return語句。

gcc的警告提示選項有很多種類型,主要可分為“-wall”類和非“-wall”類。

1.wall類警告提示

這一類警告提示選項占了gcc警告選項的90%以上,它不僅包含打開所有警告等功能,還可以單獨對常見錯誤分别指定警告,這些常見的警告選項如表2.7所示(這些選項可供讀者在實際操作時查閱使用)。

《嵌入式 Linux C 語言應用程式設計(修訂版)》——2.3 嵌入式Linux編譯器GCC的使用
《嵌入式 Linux C 語言應用程式設計(修訂版)》——2.3 嵌入式Linux編譯器GCC的使用

這些警告提示讀者可以根據自己的不同情況進行相應的選擇,這裡最為常用的是“-wall”,上面的這一小段程式使用該警告提示後的結果是:

可以看出,使用‘-wall’選項找出了未使用的變量tmp以及傳回值的問題,但沒有找出無效資料類型的錯誤。

2.非wall類警告提示

非wall類的警告提示中最為常用的有以下兩種:“-ansi”和“-pedantic”。

(1)“-ansi”

該選項強制gcc生成标準文法所要求的告警資訊,盡管這還并不能保證所有沒有警告的程式都是符合ansi c标準的。使用該選項的運作結果如下所示:

可以看出,該選項并沒有發現“long long”這個無效資料類型的錯誤。

(2)“-pedantic”

該選項允許發出ansi c标準所列的全部警告資訊,同樣也保證所有沒有警告的程式都是符合ansi c标準的。使用該選項的運作結果如下所示:

可以看出,使用該選項檢視出了“long long”這個無效資料類型的錯誤。

1.linux函數庫介紹

函數庫可以看做是事先編寫的函數集合,它可以與主函數分離,進而增加程式開發的複用性。linux中函數庫可以有3種使用的形式:靜态、共享和動态。

靜态庫的代碼在編譯時就已連接配接到開發人員開發的應用程式中,而共享庫隻是在程式開始運作時才載入。

動态庫也是在程式運作時載入,但與共享庫不同的是,動态庫使用的庫函數不是在程式運作使開始載入,而是在程式中的語句需要使用該函數時才載入。動态庫可以在程式運作期間釋放動态庫所占用的記憶體,騰出空間供其他程式使用。

由于共享庫和動态庫并沒有在程式中包括庫函數的内容,隻是包含了對庫函數的引用,是以代碼的規模比較小。

系統中可用的庫都存放在/usr/lib和/lib目錄中。庫檔案名由字首lib和庫名以及字尾組成。根據庫的類型不同,字尾名也不一樣。

如:數學共享庫的庫名為libm.so.5,這裡的辨別字元為m,版本号為5,libm.a則是靜态數學庫。在linux系統中系統所用的庫都存放在/usr/lib和/lib目錄中。

2.相關路徑選項

由于庫檔案的通常路徑不是在系統預設的路徑下,是以,首先要使用調用路徑選項來指定相關的庫檔案位置,這裡首先講解兩個常用選項的使用方法。

(1)“-i dir”

在gcc中使用頭檔案在預設情況下是在主程式中所設定的路徑,那麼如果想要改變該路徑,使用者則可以使用“-i”選項。“-i dir”選項可以在頭檔案的搜尋路徑清單中添加dir目錄。這時,gcc就會到相應的位置查找對應的目錄。

比如在“/root/workplace/gcc”下有兩個檔案:

這樣,就可在gcc指令行中加入“-i”選項,其指令如下所示。

這樣,gcc就能夠執行出正确結果。

(2)“-l dir”

選項“-l dir”的功能與“-i dir”類似,其差別就在于“-l”選項是用于指明庫檔案的路徑。例如有程式hello_sq.c需要用到目錄“/root/workplace/gcc/lib”下的一個動态庫libsunq.so,則隻需鍵入如下指令即可。

3.使用3種類型連結庫

使用上述3種類型的連結庫的方法很相似,都是使用選項是“-l”(注意這裡是小寫的“l”)。該選項是用于指明具體使用的庫檔案。由于在linux中函數庫的命名規則都是以“lib”開頭的,是以,這裡的庫檔案隻需填寫lib之後的内容即可。

如:有靜态庫檔案libm.a,在調用時隻需寫作“-lm”;同樣對于動态庫檔案libm.so;在調用時也隻需寫作“-lm”即可,其整體調用指令類似如下:

那麼,若系統中同時存在檔案名相同的靜态庫檔案和動态庫檔案時,該連結選項究竟會調用靜态庫檔案還是動态庫檔案呢?

經測試後可以發現,系統調用的是動态庫檔案,這是由于linux系統中預設的是采用動态連結的方式。這樣,若使用者要調用含有同名動态庫檔案的靜态庫檔案,則在“-l”後需要顯示地寫出包含字尾名的檔案名,如:要調用libm.a庫檔案時就需寫作“-llibm.a”。

gcc可以對代碼進行優化,它通過編譯選項-on來控制優化代碼的生成,其中n是一個代表優化級别的整數。對于不同版本的gcc來講,n的取值範圍及其對應的優化效果可能并不完全相同,比較典型的範圍是從0變化到2或3。

不同的優化級别對應不同的優化處理工作,如使用優化選項-o主要進行線程跳轉(thread jump)和延遲退棧(deferred stack pops)兩種優化。

使用優化選項-o2除了完成所有-o1級别的優化之外,同時還要進行一些額外的調整工作,如處理器指令排程等;選項-o3則還包括循環展開和其他一些與處理器特性相關的優化工作。

雖然優化選項可以加速代碼的運作速度,但對于調試而言将是一個很大的挑戰。因為代碼在經過優化之後,原先在源程式中聲明和使用的變量很可能不再使用,控制流也可能會突然跳轉到意外的地方,循環語句也有可能因為循環展開而變得到處都有,所有這些都将使調試工作異常堅難。

建議在調試的時候最好不使用任何優化選項,隻有當程式在最終發行的時候才考慮對其進行優化。

繼續閱讀