新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > Linux操作系統(tǒng)中GCC的應(yīng)用介紹(上)

Linux操作系統(tǒng)中GCC的應(yīng)用介紹(上)

——
作者: 時(shí)間:2007-04-18 來(lái)源: 收藏

在為L(zhǎng)inux開(kāi)發(fā)應(yīng)用程序時(shí),絕大多數(shù)情況下使用的都是C語(yǔ)言,因此幾乎每一位Linux程序員面臨的首要問(wèn)題都是如何靈活運(yùn)用C。目前Linux 下最常用的C語(yǔ)言是GCC(GNU Compiler Collection),它是GNU項(xiàng)目中符合ANSI C標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語(yǔ)言編寫(xiě)的程序。GCC不僅功能非常強(qiáng)大,結(jié)構(gòu)也異常靈活。最值得稱道的一點(diǎn)就是它可以通過(guò)不同的前端模塊來(lái)支持各種語(yǔ)言,如Java、 Fortran、Pascal、Modula-3和Ada等。

開(kāi)放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現(xiàn)就是程序員通過(guò)它能夠更好地控制整個(gè)編譯過(guò)程。在使用GCC編譯程序時(shí),編譯過(guò)程可以被細(xì)分為四個(gè)階段:

◆ 預(yù)處理(Pre-Processing)

◆ 編譯(Compiling)

◆ 匯編(Assembling)

◆ 鏈接(Linking)

Linux程序員可以根據(jù)自己的需要讓 GCC在編譯的任何階段結(jié)束,以便檢查或使用在該階段的輸出信息,或者對(duì)最后生成的二進(jìn)制文件進(jìn)行控制,以便通過(guò)加入不同數(shù)量和種類的調(diào)試代碼來(lái)為今后的調(diào)試做好準(zhǔn)備。和其它常用的編譯器一樣,GCC也提供了靈活而強(qiáng)大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼。

GCC提供了30多條警告信息和三個(gè)警告級(jí)別,使用它們有助于增強(qiáng)程序的穩(wěn)定性和可移植性。此外,GCC還對(duì)標(biāo)準(zhǔn)的C和C++語(yǔ)言進(jìn)行了大量的擴(kuò)展,提高程序的執(zhí)行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量。

GCC起步

在學(xué)習(xí)使用GCC之前,下面的這個(gè)例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運(yùn)用到實(shí)際的項(xiàng)目開(kāi)發(fā)中去。首先用熟悉的輸入清單1所示的代碼:

清單1:hello.c

#include 
int main(void)
{
printf ("Hello world, Linux programming!\n");
return 0;
}

然后執(zhí)行下面的命令編譯和運(yùn)行這段程序:

# gcc hello.c -o hello
# ./hello
Hello world, Linux programming!

從程序員的角度看,只需簡(jiǎn)單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來(lái)看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調(diào)用預(yù)處理程序 cpp,由它負(fù)責(zé)展開(kāi)在源文件中定義的宏,并向其中插入“#include”語(yǔ)句所包含的內(nèi)容;接著,GCC會(huì)調(diào)用ccl和as將處理后的源代碼編譯成目標(biāo)代碼;最后,GCC會(huì)調(diào)用鏈接程序ld,把生成的目標(biāo)代碼鏈接成一個(gè)可執(zhí)行程序。

為了更好地理解GCC的工作過(guò)程,可以把上述編譯過(guò)程分成幾個(gè)步驟單獨(dú)進(jìn)行,并觀察每步的運(yùn)行結(jié)果。第一步是進(jìn)行預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過(guò)程:

# gcc -E hello.c -o hello.i

此時(shí)若查看hello.cpp文件中的內(nèi)容,會(huì)發(fā)現(xiàn)stdio.h的內(nèi)容確實(shí)都插到文件里去了,而其它應(yīng)當(dāng)被預(yù)處理的宏定義也都做了相應(yīng)的處理。下一步是將hello.i編譯為目標(biāo)代碼,這可以通過(guò)使用-c參數(shù)來(lái)完成:

# gcc -c hello.i -o hello.o

GCC默認(rèn)將.i文件看成是預(yù)處理后的C語(yǔ)言源代碼,因此上述命令將自動(dòng)跳過(guò)預(yù)處理步驟而開(kāi)始執(zhí)行編譯過(guò)程,也可以使用-x參數(shù)讓GCC從指定的步驟開(kāi)始編譯。最后一步是將生成的目標(biāo)文件鏈接成可執(zhí)行文件:

# gcc hello.o -o hello

在為L(zhǎng)inux開(kāi)發(fā)應(yīng)用程序時(shí),絕大多數(shù)情況下使用的都是C語(yǔ)言,因此幾乎每一位Linux程序員面臨的首要問(wèn)題都是如何靈活運(yùn)用C編譯器。目前Linux 下最常用的C語(yǔ)言編譯器是GCC(GNU Compiler Collection),它是GNU項(xiàng)目中符合ANSI C標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語(yǔ)言編寫(xiě)的程序。GCC不僅功能非常強(qiáng)大,結(jié)構(gòu)也異常靈活。最值得稱道的一點(diǎn)就是它可以通過(guò)不同的前端模塊來(lái)支持各種語(yǔ)言,如Java、 Fortran、Pascal、Modula-3和Ada等。

{{分頁(yè)}}

開(kāi)放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現(xiàn)就是程序員通過(guò)它能夠更好地控制整個(gè)編譯過(guò)程。在使用GCC編譯程序時(shí),編譯過(guò)程可以被細(xì)分為四個(gè)階段:

◆ 預(yù)處理(Pre-Processing)

◆ 編譯(Compiling)

◆ 匯編(Assembling)

◆ 鏈接(Linking)

Linux程序員可以根據(jù)自己的需要讓 GCC在編譯的任何階段結(jié)束,以便檢查或使用編譯器在該階段的輸出信息,或者對(duì)最后生成的二進(jìn)制文件進(jìn)行控制,以便通過(guò)加入不同數(shù)量和種類的調(diào)試代碼來(lái)為今后的調(diào)試做好準(zhǔn)備。和其它常用的編譯器一樣,GCC也提供了靈活而強(qiáng)大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼。

GCC提供了30多條警告信息和三個(gè)警告級(jí)別,使用它們有助于增強(qiáng)程序的穩(wěn)定性和可移植性。此外,GCC還對(duì)標(biāo)準(zhǔn)的C和C++語(yǔ)言進(jìn)行了大量的擴(kuò)展,提高程序的執(zhí)行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量。

GCC起步

在學(xué)習(xí)使用GCC之前,下面的這個(gè)例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運(yùn)用到實(shí)際的項(xiàng)目開(kāi)發(fā)中去。首先用熟悉的輸入清單1所示的代碼:

清單1:hello.c

#include 
int main(void)
{
printf ("Hello world, Linux programming!\n");
return 0;
}

然后執(zhí)行下面的命令編譯和運(yùn)行這段程序:

# gcc hello.c -o hello
# ./hello
Hello world, Linux programming!

從程序員的角度看,只需簡(jiǎn)單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來(lái)看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調(diào)用預(yù)處理程序 cpp,由它負(fù)責(zé)展開(kāi)在源文件中定義的宏,并向其中插入“#include”語(yǔ)句所包含的內(nèi)容;接著,GCC會(huì)調(diào)用ccl和as將處理后的源代碼編譯成目標(biāo)代碼;最后,GCC會(huì)調(diào)用鏈接程序ld,把生成的目標(biāo)代碼鏈接成一個(gè)可執(zhí)行程序。

為了更好地理解GCC的工作過(guò)程,可以把上述編譯過(guò)程分成幾個(gè)步驟單獨(dú)進(jìn)行,并觀察每步的運(yùn)行結(jié)果。第一步是進(jìn)行預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過(guò)程:

# gcc -E hello.c -o hello.i

此時(shí)若查看hello.cpp文件中的內(nèi)容,會(huì)發(fā)現(xiàn)stdio.h的內(nèi)容確實(shí)都插到文件里去了,而其它應(yīng)當(dāng)被預(yù)處理的宏定義也都做了相應(yīng)的處理。下一步是將hello.i編譯為目標(biāo)代碼,這可以通過(guò)使用-c參數(shù)來(lái)完成:

# gcc -c hello.i -o hello.o

GCC默認(rèn)將.i文件看成是預(yù)處理后的C語(yǔ)言源代碼,因此上述命令將自動(dòng)跳過(guò)預(yù)處理步驟而開(kāi)始執(zhí)行編譯過(guò)程,也可以使用-x參數(shù)讓GCC從指定的步驟開(kāi)始編譯。最后一步是將生成的目標(biāo)文件鏈接成可執(zhí)行文件:

# gcc hello.o -o hello

在采用模塊化的設(shè)計(jì)思想進(jìn)行軟件開(kāi)發(fā)時(shí),通常整個(gè)程序是由多個(gè)源文件組成的,相應(yīng)地也就形成了多個(gè)編譯單元,使用GCC能夠很好地管理這些編譯單元。假設(shè)有一個(gè)由foo1.c和foo2.c兩個(gè)源文件組成的程序,為了對(duì)它們進(jìn)行編譯,并最終生成可執(zhí)行程序foo,可以使用下面這條命令:

# gcc foo1.c foo2.c -o foo

如果同時(shí)處理的文件不止一個(gè),GCC仍然會(huì)按照預(yù)處理、編譯和鏈接的過(guò)程依次進(jìn)行。如果深究起來(lái),上面這條命令大致相當(dāng)于依次執(zhí)行如下三條命令:

# gcc -c foo1.c -o foo1.o
# gcc -c foo2.c -o foo2.o
# gcc foo1.o foo2.o -o foo

在編譯一個(gè)包含許多源文件的工程時(shí),若只用一條GCC命令來(lái)完成編譯是非常浪費(fèi)時(shí)間的。假設(shè)項(xiàng)目中有100個(gè)源文件需要編譯,并且每個(gè)源文件中都包含 10000行代碼,如果像上面那樣僅用一條GCC命令來(lái)完成編譯工作,那么GCC需要將每個(gè)源文件都重新編譯一遍,然后再全部連接起來(lái)。很顯然,這樣浪費(fèi)的時(shí)間相當(dāng)多,尤其是當(dāng)用戶只是修改了其中某一個(gè)文件的時(shí)候,完全沒(méi)有必要將每個(gè)文件都重新編譯一遍,因?yàn)楹芏嘁呀?jīng)生成的目標(biāo)文件是不會(huì)改變的。要解決這個(gè)問(wèn)題,關(guān)鍵是要靈活運(yùn)用GCC,同時(shí)還要借助像Make這樣的工具。

警告提示功能

GCC包含完整的出錯(cuò)檢查和警告提示功能,它們可以幫助Linux程序員寫(xiě)出更加專業(yè)和優(yōu)美的代碼。先來(lái)讀讀清單2所示的程序,這段代碼寫(xiě)得很糟糕,仔細(xì)檢查一下不難挑出很多毛病:

{{分頁(yè)}}

◆main函數(shù)的返回值被聲明為void,但實(shí)際上應(yīng)該是int;

◆使用了GNU語(yǔ)法擴(kuò)展,即使用long long來(lái)聲明64位整數(shù),不符合ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn);

◆main函數(shù)在終止前沒(méi)有調(diào)用return語(yǔ)句。

清單2:illcode.c

#include 
void main(void)
{
long long int var = 1;
printf("It is not standard C code!\n");
}

下面來(lái)看看GCC是如何幫助程序員來(lái)發(fā)現(xiàn)這些錯(cuò)誤的。當(dāng)GCC在編譯不符合ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)的源代碼時(shí),如果加上了-pedantic選項(xiàng),那么使用了擴(kuò)展語(yǔ)法的地方將產(chǎn)生相應(yīng)的警告信息:

# gcc -pedantic illcode.c -o illcode
illcode.c: In function `main':
illcode.c:9: ISO C89 does not support `long long'
illcode.c:8: return type of `main' is not `int'

需要注意的是,-pedantic編譯選項(xiàng)并不能保證被編譯程序與ANSI/ISO C標(biāo)準(zhǔn)的完全兼容,它僅僅只能用來(lái)幫助Linux程序員離這個(gè)目標(biāo)越來(lái)越近?;蛘邠Q句話說(shuō),-pedantic選項(xiàng)能夠幫助程序員發(fā)現(xiàn)一些不符合ANSI/ISO C標(biāo)準(zhǔn)的代碼,但不是全部,事實(shí)上只有ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)中要求進(jìn)行編譯器診斷的那些情況,才有可能被GCC發(fā)現(xiàn)并提出警告。

除了-pedantic之外,GCC還有一些其它編譯選項(xiàng)也能夠產(chǎn)生有用的警告信息。這些選項(xiàng)大多以-W開(kāi)頭,其中最有價(jià)值的當(dāng)數(shù)-Wall了,使用它能夠使GCC產(chǎn)生盡可能多的警告信息:

# gcc -Wall illcode.c -o illcode
illcode.c:8: warning: return type of `main' is not `int'
illcode.c: In function `main':
illcode.c:9: warning: unused variable `var'
 

GCC給出的警告信息雖然從嚴(yán)格意義上說(shuō)不能算作是錯(cuò)誤,但卻很可能成為錯(cuò)誤的棲身之所。一個(gè)優(yōu)秀的Linux程序員應(yīng)該盡量避免產(chǎn)生警告信息,使自己的代碼始終保持簡(jiǎn)潔、優(yōu)美和健壯的特性。

在處理警告方面,另一個(gè)常用的編譯選項(xiàng)是-Werror,它要求GCC將所有的警告當(dāng)成錯(cuò)誤進(jìn)行處理,這在使用自動(dòng)編譯工具(如Make等)時(shí)非常有用。如果編譯時(shí)帶上-Werror選項(xiàng),那么GCC會(huì)在所有產(chǎn)生警告的地方停止編譯,迫使程序員對(duì)自己的代碼進(jìn)行修改。只有當(dāng)相應(yīng)的警告信息消除時(shí),才可能將編譯過(guò)程繼續(xù)朝前推進(jìn)。執(zhí)行情況如下:

# gcc -Wall -Werror illcode.c -o illcode
cc1: warnings being treated as errors
illcode.c:8: warning: return type of `main' is not `int'
illcode.c: In function `main':
illcode.c:9: warning: unused variable `var'

對(duì)Linux程序員來(lái)講,GCC給出的警告信息是很有價(jià)值的,它們不僅可以幫助程序員寫(xiě)出更加健壯的程序,而且還是跟蹤和調(diào)試程序的有力工具。建議在用GCC編譯源代碼時(shí)始終帶上-Wall選項(xiàng),并把它逐漸培養(yǎng)成為一種習(xí)慣,這對(duì)找出常見(jiàn)的隱式編程錯(cuò)誤很有幫助。

庫(kù)依賴

在Linux 下開(kāi)發(fā)軟件時(shí),完全不使用第三方函數(shù)庫(kù)的情況是比較少見(jiàn)的,通常來(lái)講都需要借助一個(gè)或多個(gè)函數(shù)庫(kù)的支持才能夠完成相應(yīng)的功能。從程序員的角度看,函數(shù)庫(kù)實(shí)際上就是一些頭文件(.h)和庫(kù)文件(.so或者.a)的集合。雖然Linux下的大多數(shù)函數(shù)都默認(rèn)將頭文件放到/usr/include/目錄下,而庫(kù)文件則放到/usr/lib/目錄下,但并不是所有的情況都是這樣。正因如此,GCC在編譯時(shí)必須有自己的辦法來(lái)查找所需要的頭文件和庫(kù)文件。

GCC采用搜索目錄的辦法來(lái)查找所需要的文件,-I選項(xiàng)可以向GCC的頭文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/include/目錄下有編譯時(shí)所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項(xiàng):

# gcc foo.c -I /home/xiaowp/include -o foo

同樣,如果使用了不在標(biāo)準(zhǔn)位置的庫(kù)文件,那么可以通過(guò)-L選項(xiàng)向GCC的庫(kù)文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫(kù)文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:

# gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

值得好好解釋一下的是-l選項(xiàng),它指示GCC去連接庫(kù)文件libfoo.so。Linux下的庫(kù)文件在命名時(shí)有一個(gè)約定,那就是應(yīng)該以lib三個(gè)字母開(kāi)頭,由于所有的庫(kù)文件都遵循了同樣的規(guī)范,因此在用-l選項(xiàng)指定鏈接的庫(kù)文件名時(shí)可以省去lib三個(gè)字母,也就是說(shuō)GCC在對(duì)-lfoo進(jìn)行處理時(shí),會(huì)自動(dòng)去鏈接名為libfoo.so的文件。

Linux下的庫(kù)文件分為兩大類分別是動(dòng)態(tài)鏈接庫(kù)(通常以.so結(jié)尾)和靜態(tài)鏈接庫(kù)(通常以.a結(jié)尾),兩者的差別僅在程序執(zhí)行時(shí)所需的代碼是在運(yùn)行時(shí)動(dòng)態(tài)加載的,還是在編譯時(shí)靜態(tài)加載的。默認(rèn)情況下,GCC在鏈接時(shí)優(yōu)先使用動(dòng)態(tài)鏈接庫(kù),只有當(dāng)動(dòng)態(tài)鏈接庫(kù)不存在時(shí)才考慮使用靜態(tài)鏈接庫(kù),如果需要的話可以在編譯時(shí)加上-static選項(xiàng),強(qiáng)制使用靜態(tài)鏈接庫(kù)。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫(kù)文件libfoo.so和libfoo.a,為了讓 GCC在鏈接時(shí)只用到靜態(tài)鏈接庫(kù),可以使用下面的命令:

# gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo

c語(yǔ)言相關(guān)文章:c語(yǔ)言教程


linux相關(guān)文章:linux教程


c++相關(guān)文章:c++教程




評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉