利用GCC開(kāi)發(fā)C程序
原創(chuàng)我們知道,程序可能是由一個(gè)源文件編譯而來(lái)的,也可能是通過(guò)編譯多個(gè)源文件得到的,并且有時(shí)候還要用到系統(tǒng)程序庫(kù)和頭文件。這里所謂構(gòu)建或編譯,就是把用程序設(shè)計(jì)語(yǔ)言(例如C或者C++編程語(yǔ)言)編寫(xiě)的文本式的源代碼轉(zhuǎn)換成用來(lái)控制中央處理器的機(jī)器代碼,這些機(jī)器代碼不是文本,而是一連串的1和0。之后,這些機(jī)器代碼被存放到一個(gè)文件中,該文件就是通常所說(shuō)的可執(zhí)行文件,有時(shí)候也叫做二進(jìn)制文件。
一、編譯C程序
對(duì)于C語(yǔ)言來(lái)說(shuō),最經(jīng)典的示例代碼莫過(guò)于著名的Hello World了,下面是它的源代碼:
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
} |
我們這里假設(shè)上述源代碼存儲(chǔ)在一個(gè)稱為“hello.c”的文件中。若要借助gcc編譯這個(gè)“hello.c”文件的話,可以使用下列命令:
$ gcc -Wall hello.c -o hello
上述命令會(huì)把“hello.c”文件中的源代碼編譯成機(jī)器代碼,并將其放到一個(gè)稱為“hello”的可執(zhí)行文件中。其中選項(xiàng)“-o”告訴gcc輸出一個(gè)包含機(jī)器代碼的文件,該選項(xiàng)通常作為命令行的最后一個(gè)參數(shù);如果省略了該選項(xiàng),那么編譯輸出將寫(xiě)到一個(gè)名為“a.out”的缺省文件中。
請(qǐng)注意,如果當(dāng)前目錄中的文件與生成的可執(zhí)行文件同名的話,原來(lái)的文件將被覆蓋掉。
選項(xiàng)“-Wall”的作用是打開(kāi)編譯程序所有最常用的警告,一般建議總是使用該選項(xiàng)。雖然還有其它的警告選項(xiàng),但是“-Wall”選項(xiàng)是最重要的一個(gè)。GCC不會(huì)生成任何警告,除非您啟用了相應(yīng)的選項(xiàng)。當(dāng)利用C和C++進(jìn)行程序設(shè)計(jì)的時(shí)候,編譯程序的警告信息對(duì)于檢測(cè)程序的問(wèn)題來(lái)說(shuō)是非常重要的。
本例中,即使使用了“-Wall”選項(xiàng)編譯程序也不會(huì)生成任何警告,因?yàn)檫@個(gè)程序是完全正確的。如果源代碼沒(méi)有導(dǎo)致任何警告,則說(shuō)明編譯很順利。若要運(yùn)行該程序,可以鍵入該可執(zhí)行文件的路徑名,如下所示:
$ ./hello
Hello, world!
上述命令將可執(zhí)行文件裝入內(nèi)存,并啟動(dòng)CPU執(zhí)行這段內(nèi)存中的指令。這里的路徑./表示當(dāng)前目錄,所以 ./hello表示加載并且運(yùn)行位于當(dāng)前目錄中的可執(zhí)行文件“hello”。
二、 查找程序的錯(cuò)誤
如前所述,當(dāng)使用C和C++進(jìn)行編程的時(shí)候,編譯程序警告對(duì)編程有著莫大的幫助。 為例說(shuō)明這一點(diǎn),我們?cè)谙旅娴某绦虼a中故意放進(jìn)了一個(gè)細(xì)微的錯(cuò)誤:它不正確地使用了printf函數(shù)的時(shí)候,因?yàn)樗褂酶↑c(diǎn)格式來(lái)輸出一個(gè)整數(shù)值。
#include <stdio.h>
int main (void)
{
printf ("Two plus two is %f\n", 4);
return 0;
} |
乍一看,很難發(fā)現(xiàn)這個(gè)錯(cuò)誤,但是如果在編譯的時(shí)候使用了“-Wall”選項(xiàng)的話,編譯程序就很容易發(fā)現(xiàn)這個(gè)問(wèn)題。在利用警告選項(xiàng)“-Wall”編譯上面的“bad.c”這個(gè)程序的時(shí)候,會(huì)收到下列消息:
$ gcc -Wall bad.c -o bad bad.c: In function ‘main’: bad.c:6: warning: double format, different type arg (arg 2) |
該消息指出,在“bad.c”文件內(nèi)的第6行錯(cuò)誤地使用了一個(gè)格式串。實(shí)際上,GCC生成的消息有一個(gè)固定的格式,即行號(hào):消息。編譯程序?qū)χ率咕幾g失敗的錯(cuò)誤信息和警告信息進(jìn)行區(qū)別對(duì)待,警告信息只是指出可能的問(wèn)題,但是不會(huì)停止程序的編譯。本例中,正確的格式說(shuō)明符應(yīng)該是“%d”,關(guān)于格式說(shuō)明符的用法,讀者可以參考有關(guān)C語(yǔ)言手冊(cè)。
如果不使用警告選項(xiàng)“-Wall”的話,程序在編譯的時(shí)候毫無(wú)異常,但是在執(zhí)行的時(shí)候卻會(huì)得到錯(cuò)誤的結(jié)果:
$ gcc bad.c -o bad $ ./bad Two plus two is 2.585495 (呵呵,結(jié)果是不是有點(diǎn)出人意料呀?!) |
我們看到,錯(cuò)誤的格式說(shuō)明符導(dǎo)致了錯(cuò)誤的結(jié)果輸出,因?yàn)槲覀儌鬟f給printf函數(shù)的是一個(gè)整數(shù)而非浮點(diǎn)數(shù)。在內(nèi)存中,整數(shù)和浮點(diǎn)數(shù)是以不同的形式存放的,并且所占用的字節(jié)數(shù)通常也不同,所以最終導(dǎo)致了一個(gè)不合邏輯的結(jié)果。當(dāng)然,在您實(shí)際運(yùn)行上述程序的時(shí)候,得到的結(jié)果可能跟這里顯示的不盡相同,這要取決于您所使用的具體硬件平臺(tái)以及操作系統(tǒng)。
很明顯,在開(kāi)發(fā)程序的時(shí)候如果不使用編譯程序的警告進(jìn)行檢驗(yàn)將是非常危險(xiǎn)的。因?yàn)榧词钩绦蛑械暮瘮?shù)使用不當(dāng)沒(méi)有導(dǎo)致程序崩潰的話,也會(huì)導(dǎo)致錯(cuò)誤的結(jié)果,而后者的危害往往更大。所以一定記得打開(kāi)編譯程序的警告選項(xiàng)“-Wall”,這會(huì)為您捕捉到C語(yǔ)言編程時(shí)最常見(jiàn)的錯(cuò)誤。
#p#
三、編制多個(gè)源文件
很多時(shí)候,一個(gè)程序會(huì)分解成多個(gè)文件分別編寫(xiě),特別是對(duì)于大型程序,這會(huì)不僅使得它更易于編輯和理解,還允許我們對(duì)個(gè)別部分進(jìn)行單獨(dú)的編譯。在下面的例子中,我們會(huì)把Hello World程序分解到三個(gè)文件中,即“main.c”、“hello_fn.c”以及頭文件“hello.h”。下面是主程序“main.c”的代碼:
#include "hello.h"
int main (void)
{
hello ("world");
return 0;
} |
在前面的“hello.c”程序中,我們是對(duì)系統(tǒng)函數(shù)printf進(jìn)行了調(diào)用;而這里沒(méi)有調(diào)用系統(tǒng)函數(shù)printf,而是調(diào)用了一個(gè)新的外部函數(shù)hello,這個(gè)外部函數(shù)定義在一個(gè)單獨(dú)的“hello_fn.c”文件中。
主程序還包含進(jìn)了頭文件“hello.h”,這個(gè)頭文件存放有hello函數(shù)的聲明。聲明用來(lái)保證函數(shù)調(diào)用和函數(shù)定義時(shí)的參數(shù)和返回值類型能夠正確匹配。在“main.c”中,我們不必包含系統(tǒng)的“stdio.h”頭文件來(lái)對(duì)printf函數(shù)進(jìn)行聲明,之所以這樣是因?yàn)椤癿ain.c”文件并沒(méi)有直接調(diào)用printf函數(shù)。實(shí)際上,在“hello.h”中只有一行聲明,用以說(shuō)明hello函數(shù)的原型:
void hello (const char * name);
Hello函數(shù)本身的定義位于“hello_fn.c”文件中:
#include <stdio.h>
#include "hello.h"
void hello (const char * name)
{
printf ("Hello, %s!\n", name);
} |
這個(gè)函數(shù)顯示消息“Hello,name!”,當(dāng)然這里的name實(shí)際會(huì)被參數(shù)name所指的字符串所替代。對(duì)于#include "FILE.h" 和#include
$ gcc -Wall main.c hello_fn.c -o newhello
本例中,我們使用“-o”選項(xiàng)為可執(zhí)行代碼指定輸出文件:“newhello”。 需要注意的是,我們這里沒(méi)有在命令行的文件列表中指定頭文件“hello.h”,因?yàn)樵谠次募械膫沃噶?include "hello.h" 已經(jīng)通知編譯程序在適當(dāng)?shù)臅r(shí)候自動(dòng)包含該文件。若要運(yùn)行該程序,鍵入這個(gè)可執(zhí)行文件的路徑名即可,如下所示:
$ ./newhello
Hello, world!
現(xiàn)在,程序的所有部分已被編譯成單個(gè)可執(zhí)行文件,這個(gè)文件的執(zhí)行結(jié)果跟前面用單個(gè)源文件編譯得到的可執(zhí)行文件是一致的。
四、文件的單獨(dú)編譯
如果程序存儲(chǔ)在一個(gè)單一的文件中的話,只要改變其中的任何一個(gè)函數(shù),整個(gè)程序就得重新編譯,以生成一個(gè)新的可執(zhí)行文件。如果源文件個(gè)頭很大的話,這時(shí)非常費(fèi)時(shí)間的。
如果程序存儲(chǔ)在不同的獨(dú)立的源文件中的話,哪些源代碼改變了,只是重新編譯相應(yīng)的文件即可。通過(guò)這種兩步走的方式,對(duì)修改后的源文件單獨(dú)編譯之后,再將它們連接起來(lái)就行了。
在第一步中,文件編譯后得到的并非一個(gè)可執(zhí)行文件,而是一個(gè)目標(biāo)文件,使用GCC時(shí)其擴(kuò)展名通常為“.o”。
在第二階段,通過(guò)一個(gè)稱為鏈接器的獨(dú)立程序?qū)⑦@些目標(biāo)文件合并起來(lái)。最后,鏈接器把所有目標(biāo)文件組織成一個(gè)單獨(dú)的可執(zhí)行文件。目標(biāo)文件中存放的是機(jī)器代碼,但是對(duì)于所有引用的在其他文件中函數(shù)或者變量的內(nèi)存地址都保持未定義狀態(tài)。這樣一來(lái),就允許編譯源文件而不會(huì)彼此直接引用。當(dāng)鏈接器生成可執(zhí)行文件的時(shí)候,它才會(huì)填上這些“遺漏”的地址。
從源文件創(chuàng)建目標(biāo)文件
命令行選項(xiàng)“-c”用來(lái)將一個(gè)源文件編譯成一個(gè)目標(biāo)文件,例如,以下命令將源文件編譯為一個(gè)目標(biāo)文件:
$ gcc -Wall -c main.c
這會(huì)生成一個(gè)名為“main.o”的目標(biāo)文件,其中存放的是main函數(shù)的機(jī)器代碼。此外,它還包含一個(gè)對(duì)外部函數(shù)hello的引用,不過(guò)在目前階段相應(yīng)的內(nèi)存地址保持為未定義狀態(tài),等到后面的鏈接階段才會(huì)填上這些內(nèi)存地址??梢允褂孟铝忻顏?lái)編譯“hello_fn.c”源文件中的hello函數(shù) :
$ gcc -Wall -c hello_fn.c
上述命令將生成一個(gè)目標(biāo)文件,名為“hello_fn.o”。 注意,在本例中我們沒(méi)有使用“-o”選項(xiàng)來(lái)為輸出的文件指定名稱。在使用“-c”選項(xiàng)的時(shí)候,編譯程序會(huì)自動(dòng)創(chuàng)建一個(gè)跟源文件同名的目標(biāo)文件,并用“.o”代替原先的擴(kuò)展名。同時(shí),我們也不必在命令行中放上“hello.h”頭文件,因?yàn)椤癿ain.c”和“hello_fn.c”文件中的#include語(yǔ)句會(huì)自動(dòng)包含這個(gè)頭文件。
從目標(biāo)文件創(chuàng)建可執(zhí)行文件
在創(chuàng)建一個(gè)可執(zhí)行文件的時(shí)候,最后一步就是使用gcc將各個(gè)目標(biāo)文件鏈接到一起,并填上外部函數(shù)的內(nèi)存地址。為了把各個(gè)目標(biāo)文件連接在一起,可以使用下列命令:
$ gcc main.o hello_fn.o -o hello
由于各個(gè)單獨(dú)的源文件已經(jīng)成功地編譯成了目標(biāo)代碼,所以這里就不必使用“-Wall”警告選項(xiàng)了。源文件一旦編譯好,鏈接就成為一個(gè)無(wú)歧義的過(guò)程,它要么成功,要么失敗——并且,只有在目標(biāo)文件中存在無(wú)法解析的引用的情況下才會(huì)發(fā)生。
在鏈接階段,gcc使用的工具是鏈接器ld,這是一個(gè)獨(dú)立的程序。在GNU系統(tǒng)中使用的鏈接器是GNU ld。在其他系統(tǒng)上,GCC可能使用GNU 鏈接器,也可能使用的是它們自己的鏈接器。通過(guò)運(yùn)行鏈接器,gcc從目標(biāo)文件創(chuàng)建一個(gè)可執(zhí)行文件?,F(xiàn)在,我們可以試著運(yùn)行剛生成的可執(zhí)行文件了,命令如下所示:
$ ./hello
Hello, world!
我們看到,這個(gè)程序的輸出結(jié)果跟前面由單個(gè)源文件編譯得到的程序的結(jié)果是一樣的。
目標(biāo)文件的鏈接順序
在類UNIX系統(tǒng)上,編譯器和鏈接器的傳統(tǒng)做法是將命令行指定的目標(biāo)文件按照從左到右的順序進(jìn)行搜索。這意味著,那些含有函數(shù)定義的目標(biāo)文件應(yīng)當(dāng)放在所有調(diào)用這些函數(shù)的文件之后。本例中,包含有hello函數(shù)的“hello_fn.o”文件應(yīng)該位于“main.o”文件之后,因?yàn)閙ain函數(shù)將調(diào)用hello函數(shù):
$ gcc main.o hello_fn.o -o hello (正確的順序)
對(duì)于一些編譯器或者鏈接器來(lái)說(shuō),如果上述順序弄反了的話,就會(huì)出錯(cuò):
$ cc hello_fn.o main.o -o hello (不正確的順序) main.o: In function ‘main’: main.o(.text+0xf): undefined reference to ‘hello’ |
因?yàn)椤癿ain.o”文件后面沒(méi)有包含hello函數(shù)定義的目標(biāo)文件,所以編譯出錯(cuò)。目前大部分編譯器和鏈接器通常會(huì)搜索所有的目標(biāo)文件,而不管它們的順序如何,但是并非所有的編譯器和鏈接器都是這樣的,所以最好還是按照從左至右的順序來(lái)給目標(biāo)文件排個(gè)隊(duì)為妙。
所以,如果您不想碰到煩人的未定義的引用這類問(wèn)題的話,最好把所有必需的文件都羅列到命令行中。
#p#
五、重新編譯和重新鏈接
為了說(shuō)明如何單獨(dú)編譯某些源文件,下面我們修改一下“main.c”主程序,讓它向“所有人”而非“世界”問(wèn)好,如下所示:
#include "hello.h"
int main (void)
{
hello ("everyone"); /* changed from "world" */
return 0;
} |
更新“main.c”文件后,我們使用以下命令來(lái)重新編譯這個(gè)源文件:
$ gcc -Wall -c main.c
這將生成一個(gè)新的目標(biāo)文件:“main.o”。 這里不必為“hello_fn.c”新建一個(gè)目標(biāo)文件,因?yàn)檫@個(gè)文件以及依賴于該文件的文件如頭文件等都沒(méi)有發(fā)生任何改變。這個(gè)新的目標(biāo)文件跟hello函數(shù)重新鏈接后,會(huì)生成一個(gè)新的可執(zhí)行文件:
$ gcc main.o hello_fn.o -o hello
如今,這個(gè)新的可執(zhí)行文件將使用新的main函數(shù)來(lái)產(chǎn)生輸出:
$ ./hello
Hello, everyone!
需要注意的是,我們只是重新編譯“main.c”文件,并重新鏈接原有的目標(biāo)文件的hello函數(shù)。如果修改的是“hello_fn.c”,則可以重新編譯“hello_fn.c”來(lái)創(chuàng)建一個(gè)新的“hello_fn.o”目標(biāo)文件,并用它跟現(xiàn)有的“main.o”文件相鏈接就行了。如果修改了一個(gè)函數(shù)的原型,則必須修改所有涉及該函數(shù)其他源文件,并全部重新編譯、鏈接??偟膩?lái)說(shuō),在具有許多源文件的大型項(xiàng)目中,鏈接要比編譯快多了,所以只是重新編譯已修改過(guò)的源程序能夠節(jié)約許多時(shí)間。此外,只重新編譯項(xiàng)目中經(jīng)過(guò)修改的文件的過(guò)程還可以利用GNU Make 自動(dòng)處理。
六、鏈接外部程序庫(kù)
程序庫(kù)是一組可以鏈接到程序中的預(yù)編譯的目標(biāo)文件。程序庫(kù)最常見(jiàn)的用法就是提供系統(tǒng)函數(shù),例如C語(yǔ)言數(shù)學(xué)程序庫(kù)中的平方根函數(shù)sqrt等。程序庫(kù)通常存儲(chǔ)在一些擴(kuò)展名為“.a”的專用存檔文件中,這些就是通常所說(shuō)的靜態(tài)庫(kù)。這些文件是由一個(gè)單獨(dú)的工具即GNU歸檔程序ar從目標(biāo)文件生成的,鏈接器在編譯時(shí)會(huì)用它們來(lái)解析對(duì)函數(shù)的引用。為簡(jiǎn)單起見(jiàn),這里只介紹靜態(tài)庫(kù),至于在在運(yùn)行時(shí)動(dòng)態(tài)鏈接的共享庫(kù)將在后續(xù)文章中加以介紹。
標(biāo)準(zhǔn)的系統(tǒng)程序庫(kù)通常位于目錄“/usr/lib”和“/lib”下面。在同時(shí)支持64和32位可執(zhí)行文件的系統(tǒng)上,程序庫(kù)的64位版本經(jīng)常存放在“/usr/lib64”和“/lib64”目錄中,而32位版本則存放在“/usr/lib”和“/lib”目錄。例如,C的數(shù)學(xué)庫(kù)通常存放在類UNIX系統(tǒng)的“/usr/lib/libm.a”文件中,這個(gè)程序庫(kù)的函數(shù)的原型聲明則位于“/usr/include/math.h”頭文件內(nèi)。 C的標(biāo)準(zhǔn)程序庫(kù)則位于“/usr/lib/libc.a”,該庫(kù)中具有ANSI/ISO C 標(biāo)準(zhǔn)所規(guī)定的各種函數(shù),如printf等。默認(rèn)時(shí),所有C程序都會(huì)鏈接這個(gè)程序庫(kù)。下面是一個(gè)調(diào)用libm.a數(shù)學(xué)程序庫(kù)中的外部函數(shù)sqrt的示例程序:
#include <math.h>
#include <stdio.h>
int main (void)
{
double x = sqrt (2.0);
printf ("The square root of 2.0 is %f\n", x);
return 0;
} |
當(dāng)我們用這個(gè)單獨(dú)的源文件創(chuàng)建一個(gè)可執(zhí)行文件的時(shí)候,會(huì)在編譯階段出錯(cuò):
$ gcc -Wall calc.c -o calc /tmp/ccbR6Ojm.o: In function ‘main’: /tmp/ccbR6Ojm.o(.text+0x19): undefined reference to ‘sqrt’ |
這是由于缺少外部的數(shù)學(xué)程序庫(kù)libm.a,所以無(wú)法正確解析對(duì)sqrt函數(shù)的引用所致。函數(shù)sqrt的定義不在程序或者默認(rèn)程序庫(kù)“l(fā)ibc.a”中,而編譯程序也沒(méi)有鏈接“l(fā)ibm.a”文件,因?yàn)槲覀儧](méi)有顯式的選取這個(gè)庫(kù)。錯(cuò)誤信息中提到的“/tmp/ccbR60jm.o”文件是一個(gè)臨時(shí)的目標(biāo)文件,它是由“calc.c”生成的,用以處理鏈接過(guò)程。
要想讓編譯程序把sqrt函數(shù)鏈接到主程序“calc.c”上,我們必須為編譯程序提供“l(fā)ibm.a”程序庫(kù)。為做到這一點(diǎn),最簡(jiǎn)單的方法就是在命令行中顯式的規(guī)定這個(gè)程序庫(kù),如下所示:
$ gcc -Wall calc.c /usr/lib/libm.a -o calc
程序庫(kù)“l(fā)ibm.a”由一些存放各種數(shù)學(xué)函數(shù)的目標(biāo)文件組成,其中包括sin、cos、exp、log以及sqrt函數(shù)等等。為了找到包含sqrt函數(shù)的目標(biāo)文件,鏈接器會(huì)把“l(fā)ibm.a”程序庫(kù)的各個(gè)目標(biāo)文件仔細(xì)搜查一遍。一旦找到sqrt函數(shù)所在的目標(biāo)文件,主程序就可以鏈接這個(gè)目標(biāo)文件從而生成一個(gè)完整的可執(zhí)行文件:
$ ./calc
The square root of 2.0 is 1.414214
這個(gè)可執(zhí)行文件不僅包含主函數(shù)生成的機(jī)器代碼,同時(shí)還有從“l(fā)ibm.a”程序庫(kù)的相應(yīng)目標(biāo)文件中復(fù)制過(guò)來(lái)的sqrt函數(shù)的機(jī)器代碼。為了免去在命令行中指定冗長(zhǎng)的路徑的麻煩,編譯程序提供了一個(gè)“-l”選項(xiàng)來(lái)簡(jiǎn)化鏈接程序庫(kù)的工作,下面是一個(gè)示例:
$ gcc -Wall calc.c -lm -o calc
這個(gè)命令等價(jià)于上面使用完整庫(kù)名/usr/lib/libm.a的那條命令。一般而言,編譯程序選項(xiàng)“-lNAME”將嘗試用標(biāo)準(zhǔn)程序庫(kù)目錄中名為“l(fā)ibNAME.a”的庫(kù)文件鏈接到我們的目標(biāo)文件。當(dāng)然,我們可以通過(guò)命令行選項(xiàng)和環(huán)境變量來(lái)指定更多的目錄,這一點(diǎn)下面將會(huì)談到。 對(duì)于一個(gè)大型程序,在鏈接諸如數(shù)學(xué)程序庫(kù)、圖像程序庫(kù)以及網(wǎng)絡(luò)程序庫(kù)等程序庫(kù)的時(shí)候通常會(huì)使用許多“-l”選項(xiàng)。
下面我們探討一下程序庫(kù)的鏈接順序問(wèn)題。程序庫(kù)的搜索順序與目標(biāo)文件的搜索順序一樣,都是按照它們?cè)谠诿钚兄械呐帕袕淖蟮接乙来嗡阉鳌娣藕瘮?shù)定義的程序庫(kù)應(yīng)該出現(xiàn)在所有使用它的源文件或者目標(biāo)文件的后面。這條規(guī)則同樣適用于“-l”選項(xiàng)指定的那些程序庫(kù),如下所示:
$ gcc -Wall calc.c -lm -o calc (正確順序)
對(duì)于某些編譯器來(lái)說(shuō),如果上面的順序弄反了,比如將“-lm”放在了使用它的文件的前面,這時(shí)就會(huì)出錯(cuò):
$ cc -Wall -lm calc.c -o calc (錯(cuò)誤的順序) main.o: In function ‘main’: main.o(.text+0xf): undefined reference to ‘sqrt’ |
出錯(cuò)的原因是“calc.c”之后根本就找不到包含sqrt的程序庫(kù)或者目標(biāo)文件。選項(xiàng)“-lm”應(yīng)該放到“calc.c”文件之后。對(duì)于程序庫(kù)之間的排列順序也應(yīng)遵循這個(gè)規(guī)則,即當(dāng)一個(gè)程序庫(kù)調(diào)用定義在另一個(gè)程序庫(kù)中的外部函數(shù)的時(shí)候,這個(gè)庫(kù)必須放在包含該函數(shù)的程序庫(kù)的前面。例如,一個(gè)程序“data.c”使用了線性規(guī)劃程序庫(kù)“l(fā)ibglpk.a”,之后又用到了數(shù)學(xué)程序庫(kù)“l(fā)ibm.a”,那么編譯這個(gè)程序的命令就應(yīng)該像下面這樣:
$ gcc -Wall data.c -lglpk -lm
之所以這樣安排,是因?yàn)椤發(fā)ibglpk.a”中的目標(biāo)文件要用到定義在“l(fā)ibm.a”中的函數(shù)。 就像目標(biāo)文件那樣,目前大部分編譯器和鏈接器通常會(huì)搜索所有的目標(biāo)文件,而不管它們的順序如何,但是并非所有的編譯器和鏈接器都是這樣的,所以最好還是按照從左至右的順序來(lái)給目標(biāo)文件排個(gè)隊(duì)為妙。
#p#
七、使用程序庫(kù)的頭文件
當(dāng)我們使用程序庫(kù)的時(shí)候,為了聲明函數(shù)參數(shù)和返回值的類型,必須包含進(jìn)相應(yīng)的頭文件。如果不進(jìn)行聲明的話,可能會(huì)向函數(shù)的參數(shù)傳遞錯(cuò)誤的類型,從而導(dǎo)致錯(cuò)誤的結(jié)果。下面的例子展示了另一個(gè)在其函數(shù)中調(diào)用C數(shù)學(xué)程序庫(kù)的程序,本例中,pow函數(shù)用來(lái)計(jì)算2的立方。
#include <stdio.h>
int main (void)
{
double x = pow (2.0, 3.0);
printf ("Two cubed is %f\n", x);
return 0; |
然而,此程序中有一個(gè)錯(cuò)誤,即忘記了用#include語(yǔ)句包含“math.h”,所以編譯程序也就不知道pow函數(shù)的原型為double pow (double x, double y)。編制此程序時(shí)如果沒(méi)有使用任何警告選項(xiàng)的話,將得到一個(gè)產(chǎn)生錯(cuò)誤結(jié)果的可執(zhí)行文件:
$ gcc badpow.c -lm $ ./a.out Two cubed is 2.851120 (結(jié)果有誤,應(yīng)該是8才對(duì)) |
這里的結(jié)果是不正確的,因?yàn)檎{(diào)用pow時(shí)使用的參數(shù)和返回值類型不對(duì)。注意,該結(jié)果會(huì)隨著硬件平臺(tái)和操作系統(tǒng)的不同而有所區(qū)別。如果編譯時(shí)打開(kāi)“-Wall”警告選項(xiàng)的話,將會(huì)出現(xiàn)下面的提示:
$ gcc -Wall badpow.c -lm badpow.c: In function ‘main’: badpow.c:6: warning: implicit declaration of function ‘pow’ |
這個(gè)例子再次說(shuō)明使用警告選項(xiàng)“-Wall”檢測(cè)各種有可能被忽視的問(wèn)題的重要性。
八、結(jié)束語(yǔ)
本文講述利用gcc開(kāi)發(fā)C程序的詳細(xì)過(guò)程,即如何通過(guò)GCC來(lái)構(gòu)建或者說(shuō)是編譯C程序。我們知道,程序可能是由一個(gè)源文件編譯而來(lái)的,也可能是通過(guò)編譯多個(gè)源文件得到的,并且有時(shí)候還要用到系統(tǒng)程序庫(kù)和頭文件。本文詳細(xì)介紹了如何從單個(gè)與多個(gè)源文件來(lái)生成可執(zhí)行文件,同時(shí)還介紹了用于檢查程序錯(cuò)誤的有該選項(xiàng)以及鏈接庫(kù)程序的方法,希望本文對(duì)讀者能夠有所幫助。




















