(一) gcc的基本用法
(二) 警告提示功能選項
(三) 庫操作選項
(四) 調試選項
(五) 交叉編譯選項
(一) gcc的基本用法
使用gcc編譯器時,必須給出一系列必要的調用參數和文件名稱。不同參數的先后順序對執行結果沒有影響,只有在使用同類參數時的先后順序才需要考慮。如果使用了多個 -L 的參數來定義庫目錄,gcc會根據多個 -L 參數的先后順序來執行相應的庫目錄。
因為很多gcc參數都由多個字母組成,所以gcc參數不支持單字母的組合,Linux中常被叫短參數(short options),如 -dr 與 -d -r 的含義不一樣。gcc編譯器的調用參數大約有100多個,其中多數參數我們可能根本就用不到,這里只介紹其中最基本、最常用的參數。
gcc最基本的用法是:gcc [options] [filenames]
其中,options就是編譯器所需要的參數,filenames給出相關的文件名稱,最常用的有以下參數:
-c
只編譯,不鏈接成為可執行文件。編譯器只是由輸入的 .c 等源代碼文件生成 .o 為后綴的目標文件,通常用於編譯不包含主程序的子程序文件。
-o output_filename
確定輸出文件的名稱為output_filename。同時這個名稱不能和源文件同名。如果不給出這個選項,gcc就給出默認的可執行文件 a.out 。
-g
產生符號調試工具(GNU的 gdb)所必要的符號信息。想要對源代碼進行調試,就必須加入這個選項。
-O
對程序進行優化編譯、鏈接。采用這個選項,整個源代碼會在編譯、鏈接過程中進行優化處理,這樣產生的可執行文件的執行效率可以提高,但是編譯、鏈接的速度就相應地要慢一些,而且對執行文件的調試會產生一定的影響,造成一些執行效果與對應源文件代碼不一致等一些令人“困惑”的情況。因此,一般在編譯輸出軟件發行版時使用此選項。
-O2
比 -O 更好的優化編譯、鏈接。當然整個編譯鏈接過程會更慢。
-Idirname
將 dirname 所指出的目錄加入到程序頭文件目錄列表中,是在預編譯過程中使用的參數。
說明:
C程序中的頭文件包含兩種情況:
#include <stdio.h>
#include "stdio.h"
其中,使用尖括號(<>),預處理程序 cpp 在系統默認包含文件目錄(如/usr/include)中搜索相應的文件;使用雙引號,預處理程序 cpp 首先在當前目錄中搜尋頭文件,如果沒有找到,就到指定的 dirname 目錄中去尋找。
在程序設計中,如果需要的這種包含文件分別分布在不同的目錄中,就需要逐個使用 -I 選項給出搜索路徑。
-Ldirname
將dirname所指出的目錄加入到程序函數庫文件的目錄列表中,是在鏈接過程中使用的參數。在默認狀態下,鏈接程序 ld 在系統默認路徑中(如 /usr/lib)尋找所需要的庫文件。這個選項告訴鏈接程序,首先到 -L 指定的目錄中去尋找,然后到系統默認路徑中尋找;如果函數庫存放在多個目錄下,就需要依次使用這個選項,給出相應的存放目錄。
-lname
鏈接時裝載名為 libname.a 的函數庫。該函數庫位於系統默認的目錄或者由 -L 選項確定的目錄下。例如,-lm 表示鏈接名為 libm.a 的數學函數庫。
例子:假定有一個程序名為 test.c 的C語言源代碼文件,要生成一個可執行文件。
#include <stdio.h>
int main(void)
{
printf("Hello world/n");
return 0;
}
最簡單的辦法:gcc test.c -o test
首先,gcc需要調用預處理程序 cpp,由它負責展開在源文件中定義的宏,並向其中插入“#include”語句所包含的內容;接着,gcc調用 ccl 和 as,將處理后的源代碼編譯成目標代碼;最后,gcc調用鏈接程序 ld,把生成的目標代碼鏈接成一個可執行程序。因此,默認情況下,預編譯、編譯鏈接一次完成。
編譯過程的分步執行:
為了更好地理解gcc的工作過程,我們可以讓在gcc工作的4個階段中的任何一個階段中停止下來。相關的參數有:
-E
預編譯后停下來,生成后綴為 .i 的預編譯文件。
-c
編譯后停下來,生成后綴為 .o 的目標文件。
-S
匯編后停下來,生成后綴為 .s 的匯編源文件。
第一步:進行預編譯,使用 -E 參數
gcc -E test.c -o test.i
查看 test.i 文件中的內容,會發現 stdio.h 的內容確實都插到文件里去了,而其他應當被預處理的宏定義也都做了相應的處理。
第二步:將 test.i 編譯為目標代碼,使用 -c 參數
gcc -c test.c -o test.o
第三步:生成匯編源文件
gcc -S test.c -o test.s
第四步:將生成的目標文件鏈接成可執行文件
gcc test.o - o test
對於稍微復雜的情況,比如有多個源代碼文件、需要鏈接庫或有其他比較特別的要求,就要給定適當的調用選項參數。
例子:整個源代碼程序由兩個文件 testmain.c 和 testsub.c 組成,程序中使用了系統提供的數學庫(所有與浮點相關的數學運算都必須使用數學庫)。
gcc testmain.c testsub.c -lm -o test
其中,-lm 表示鏈接系統的數學庫 libm.a 。
說明:
在編譯一個包含許多源文件的工程時,若只用一條gcc命令來完成編譯是非常浪費時間的。假如項目中有100個源文件需要編譯,並且每個源文件中都包含一萬行代碼,如果像上面那樣僅用一條gcc命令來完成編譯工作,那么gcc需要將每個源文件都重新編譯一遍,然后再全部鏈接起來。很顯然,這樣浪費的時間相當多,尤其是當用戶只是修改了其中某個文件的時候,完全沒有必要將每個文件都重新編譯一遍,因為很多已經生成的目標文件是不會發生改變的。要解決這個問題,需要借助像make這樣的工具。
(二) 警告提示功能選項
gcc包含完整的出錯檢查和警告提示功能,它們可以幫助Linux程序員寫出更加專業的代碼。
(1) -pedantic 選項
當gcc在編譯不符合ANSI/ISO C 語言標准的源代碼時,將產生相應的警告信息。
- #include <stdio.h>
- void main(void)
- {
- long long int var = 1;
- printf("It is not standard C code!/n");
- }
它有以下問題:
> main 函數的返回值被聲明為 void,但實際上應該是 int。
> 使用了 GNU 語法擴展,即使用 long long 來聲明64位整數,不符合 ANSI/ISO C 語言標准。
> main 函數在終止前沒有調用 return 語句。
(2) -Wall 選項
除了 -pedantic 之外,gcc 還有一些其他編譯選項,也能夠產生有用的警告信息。這些選項大多以 -W 開頭。其中最有價值的當數 -Wall 了,使用它能夠使 gcc 產生盡可能多的警告信息。
gcc 給出的警告信息雖然從嚴格意義上說不能算作錯誤,但卻和可能成為錯誤來源。一個優秀的程序員應該盡量避免產生警告信息,使自己的代碼始終保持簡潔、優美和健壯的特性。
建議:gcc 給出的警告信息是很有價值的,它們不僅可以幫助程序員寫出更加健壯的程序,而且還是跟蹤和調試程序的有力工具。建議在用 gcc 編譯源代碼時始終帶上 -Wall 選項,並把它逐漸培養成一種習慣,這對找出常見的隱式編程錯誤很有幫助。
(3) -Werror 選項
在處理警告方面,另一個常用的編譯選項是 -Werror。它要求 gcc 將所有的警告當成錯誤進行處理,這在使用自動編譯工具(如 Make 等)時非常有用。如果編譯時帶上 -Werror 選項,那么 gcc 會在所有產生警告的地方停止編譯,迫使程序員對自己的代碼進行修改。只有當相應的警告信息消除時,才可能將編譯過程繼續朝前推進。
(4) -Wcast-align 選項
當源程序中地址不需要對齊的指針指向一個地址需要對齊的變量地址時,則產生一個警告。例如,char * 指向一個 int * 地址,而通常在機器中 int 變量類型是需要地址能被2或4整除的對齊地址。
(5) 其他常用選項
-v 輸出 gcc 工作的詳細過程
--target-help 顯示目前所用的gcc支持CPU類型
-Q 顯示編譯過程的統計數據和每一個函數名
(三) 庫操作選項
在Linux下開發軟件時,完全不使用第三方函數庫的情況是比較少見的,通常來講都需要借助一個或多個函數庫的支持才能夠完成相應的功能。
從程序員的角度看,函數庫實際上就是一些頭文件(.h)和庫文件(.so 或 .a)的集合。雖然Linux下的大多數函數都默認將頭文件放到 /usr/include/ 目錄下,而庫文件則放到 /usr/lib/ 目錄下,但並不是所有的情況都是這樣。正因如此,gcc 在編譯時必須有自己的辦法來查找所需要的頭文件和庫文件。常用的方法有:
(1) -I
可以向 gcc 的頭文件搜索路徑中添加新的目錄。
(2) -L
如果使用了不在標准位置的庫文件,那么可以通過 -L 選項向 gcc 的庫文件搜索路徑中添加新的目錄。
(3) -l
Linux下的庫文件在命名時有一個約定,就是應該以 lib 這3個字母開頭,由於所有的庫文件都遵循了同樣的規范,因此在用 -l 選項指定鏈接的庫文件名時可以省去 lib 這3個字母。例如,gcc 在對 -lfoo 進行處理時,會自動去鏈接名為 libfoo.so 的文件。
(4) -static
Linux下的庫文件分為兩大類,分別是:動態鏈接庫(通常以 .so 結尾)和靜態鏈接庫(通常以 .a 結尾)。
兩者的差別僅在程序執行時所需的代碼是在運行時動態加載的,還是在編譯時靜態加載的。
默認情況下,gcc 在鏈接時優先使用動態鏈接庫,只有當動態鏈接庫不存在時才考慮使用靜態鏈接庫。
如果需要的話,可以在編譯時加上 -static 選項,強制使用靜態鏈接庫。
(5) -shared
生成一個共享的目標文件,它能夠與其他的目標一起鏈接生成一個可執行的文件。
(四) 調試選項
對於Linux程序員來講,gdb(GNU Debugger)通過與 gcc 的配合使用,為基於Linux的軟件開發提供了一個完善的調試環境。常用的有:
(1) -g 和 -ggdb
默認情況下,gcc 在編譯時不會將調試符號插入到生成的二進制代碼中,因為這樣會增加可執行文件的大小。如果需要在編譯時生成調試符號信息,可以使用 gcc 的 -g 或 -ggdb 選項。
gcc 在產生調試符號時,同樣采用了分級的思路,開發人員可以通過在 -g 選項后附加數字1、2、3指定在代碼中加入調試信息的多少。默認的級別是2(-g2),此時產生的調試信息包括:擴展的符號表、行號、局部或外部變量信息。
級別3(-g3)包含級別2中的所有調試信息以及源代碼中定義的宏。
級別1(-g1)不包含局部變量和與行號有關的調試信息,因此只能夠用於回溯跟蹤和堆棧轉儲。
回溯追蹤:指的是監視程序在運行過程中函數調用歷史。
堆棧轉儲:則是一種以原始的十六進制格式保存程序執行環境的方法。
注意:使用任何一個調試選項都會使最終生成的二進制文件的大小急劇增加,同時增加程序在執行時的開銷,因此,調試選項通常僅在軟件的開發和調試階段使用。
(2) -p 和 -pg
會將剖析(Profiling)信息加入到最終生成的二進制代碼中。剖析信息對於找出程序的性能瓶頸很有幫助,是協助Linux程序員開發出高性能程序的有力工具。
(3) -save-temps
保存編譯過程中生成的一些列中間文件。
# gcc test.c -o test -save-temps
除了生成執行文件test之外,還保存了test.i 和 test.s 中間文件,供用戶查詢調試。
(五) 交叉編譯選項
通常情況下使用 gcc 編譯的目標代碼都與使用的機器是一致的,但 gcc 也支持交叉編譯的功能,能夠編譯其他不同CPU的目標代碼。
使用 gcc 開發嵌入式系統,我們幾乎都是以通用的PC機(X86)平台來做宿主機,通過 gcc 的交叉編譯功能對其他嵌入式CPU的開發任務。
(具體的選項設置,此處省略)