1. 程式人生 > >C語言的本質(33)——GCC編譯器入門

C語言的本質(33)——GCC編譯器入門

GCC(GNU CompilerCollection,GNU編譯器套裝),是由 GNU 開發的程式語言編譯器。它是以GPL許可證所發行的自由軟體,也是 GNU計劃的關鍵部分。GCC原本作為GNU作業系統的官方編譯器,現已被大多數類Unix作業系統採納為標準的編譯器,GCC同樣適用於微軟的Windows。GCC是自由軟體過程發展中的著名例子,由自由軟體基金會以GPL協議釋出。GCC是Linux平臺下最常用的編譯程式,是Linux平臺編譯器的事實標準。

程式的編譯過程

對於GUN編譯器來說,程式的編譯要經歷預處理、編譯、彙編、連線四個階段,如下圖所示:

從功能上分,預處理、編譯、彙編是三個不同的階段,但GCC的實際操作上,它可以把這三個步驟合併為一個步驟來執行。下面我們以C語言為例來談一下不同階段的輸入和輸出情況。

在預處理階段,輸入的是C語言的原始檔,通常為*.c。它們通常帶有.h之類標頭檔案的包含檔案。這個階段主要處理原始檔中的#ifdef、 #include和#define命令。該階段會生成一箇中間檔案*.i,但實際工作中通常不用專門生成這種檔案,因為基本上用不到;若非要生成這種檔案不可,可以利用下面的示例命令:

gcc -E test.c -o test.i

在編譯階段,輸入的是中間檔案*.i,編譯後生成組合語言檔案*.s 。這個階段對應的GCC命令如下所示:
GCC -S test.i -o test.s

在彙編階段,將輸入的彙編檔案*.s轉換成機器語言*.o。這個階段對應的GCC命令如下所示:

GCC -c test.s -o test.o

最後,在連線階段將輸入的機器程式碼檔案*.s(與其它的機器程式碼檔案和庫檔案)彙集成一個可執行的二進位制程式碼檔案。這一步驟,可以利用下面的示例命令完成:

GCC test.o -o test

 GCC常用模式

這裡介紹GCC追常用的兩種模式:編譯模式和編譯連線模式。下面以一個例子來說明各種模式的使用方法。為簡單起見,假設我們全部的原始碼都在一個檔案test.c中,要想把這個原始檔直接編譯成可執行程式,可以使用以下命令:

$ GCC -o test

這裡test.c是原始檔,生成的可執行程式碼存放在一個名為test 的檔案中(該檔案是機器程式碼並且可執行)。-o 是生成可執行檔案的輸出選項。如果我們只想讓原始檔生成目標檔案(給檔案雖然也是機器程式碼但不可執行),可以使用標記-c ,詳細命令如下所示:

$ GCC -c test.c

預設情況下,生成的目標檔案被命名為test.o,但我們也可以為輸出檔案指定名稱,比如:

$ GCC -c test.c -o

上面這條命令將編譯後的目標檔案命名為mytest.o,而不是預設的test.o。

迄今為止,我們談論的程式僅涉及到一個原始檔;現實中,一個程式的原始碼通常包含在多個原始檔之中,這該怎麼辦?沒關係,即使這樣,用GCC處理起來也並不複雜,比如:

 $GCC -o test  first.c second.c third.c

該命令將同時編譯三個原始檔,即first.c、second.c和 third.c,然後將它們連線成一個可執行程式,名為test。

需要注意的是,要生成可執行程式時,一個程式無論有有一個原始檔還是多個原始檔,所有被編譯和連線的原始檔中必須有且僅有一個main函式,因為main函式是該程式的入口點(換句話說,當系統呼叫該程式時,首先將控制權授予程式的main函式)。但如果僅僅是把原始檔編譯成目標檔案的時候,因為不會進行連線,所以main函式不是必需的。

gcc所遵循的部分約定規則:

.c為字尾的檔案,C語言原始碼檔案;

.a為字尾的檔案,是由目標檔案構成的檔案庫檔案;

.C,.cc或.cxx 為字尾的檔案,是C++原始碼檔案且必須要經過預處理;

.h為字尾的檔案,是程式所包含的標頭檔案;

.i 為字尾的檔案,是C原始碼檔案且不應該對其執行預處理;

.ii為字尾的檔案,是C++原始碼檔案且不應該對其執行預處理;

.m為字尾的檔案,是Objective-C原始碼檔案;

.o為字尾的檔案,是編譯後的目標檔案;

.s為字尾的檔案,是組合語言原始碼檔案;

.S為字尾的檔案,是經過預編譯的組合語言原始碼檔案。

常用選項

許多情況下,標頭檔案和原始檔會單獨存放在不同的目錄中。例如,假設存放原始檔的子目錄名為./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

在編譯命令列中定義符號常量的好處是,不必修改原始檔就能改變由符號常量控制的行為。

在使用Gcc編譯器的時候,我們必須給出一系列必要的呼叫引數和檔名稱。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 <myinc.h>
#include “myinc.h”

其中,A類使用尖括號(< >),B類使用雙引號(“”)。對於A類,預處理程式cpp在系統預設包含檔案目錄(如/usr/include)中搜尋相應的檔案,而B類,預處理程式在目標檔案的資料夾內搜尋相應檔案。

警告功能

 當GCC在編譯過程中檢查出錯誤的話,它就會中止編譯;但檢測到警告時卻能繼續編譯生成可執行程式,因為警告只是針對程式結構的診斷資訊,它不能說明程式一定有錯誤,而是存在風險,或者可能存在錯誤。雖然GCC提供了非常豐富的警告,但前提是你已經啟用了它們,否則它不會報告這些檢測到的警告。

在眾多的警告選項之中,最常用的就是-Wall選項。該選項能發現程式中一系列的常見錯誤警告,該選項用法舉例如下:

$ gcc -Wall test.c -o test