Linux下C編程基礎(chǔ)之:gcc編譯器
(4)鏈接階段。
在成功編譯之后,就進入了鏈接階段。這里涉及一個重要的概念:函數(shù)庫。
讀者可以重新查看這個小程序,在這個程序中并沒有定義“printf”的函數(shù)實現(xiàn),且在預(yù)編譯中包含進的“stdio.h”中也只有該函數(shù)的聲明,而沒有定義函數(shù)的實現(xiàn),那么,是在哪里實現(xiàn)“printf”函數(shù)的呢?最后的答案是:系統(tǒng)把這些函數(shù)的實現(xiàn)都放到名為libc.so.6的庫文件中去了,在沒有特別指定時,gcc會到系統(tǒng)默認(rèn)的搜索路徑“/usr/lib”下進行查找,也就是鏈接到libc.so.6函數(shù)庫中去,這樣就能調(diào)用函數(shù)“printf”了,而這也正是鏈接的作用。
函數(shù)庫有靜態(tài)庫和動態(tài)庫兩種。靜態(tài)庫是指編譯鏈接時,將庫文件的代碼全部加入可執(zhí)行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其后綴名通常為“.a”。動態(tài)庫與之相反,在編譯鏈接時并沒有將庫文件的代碼加入可執(zhí)行文件中,而是在程序執(zhí)行時加載庫,這樣可以節(jié)省系統(tǒng)的開銷。一般動態(tài)庫的后綴名為“.so”,如前面所述的libc.so.6就是動態(tài)庫。gcc在編譯時默認(rèn)使用動態(tài)庫。
完成了鏈接之后,gcc就可以生成可執(zhí)行文件,如下所示。
[root@localhostgcc]#gcchello.o–ohello
運行該可執(zhí)行文件,出現(xiàn)的正確結(jié)果如下。
[root@localhostgcc]#./hello
Hello!Thisisourembeddedworld!
3.3.2gcc編譯選項分析
gcc有超過100個可用選項,主要包括總體選項、告警和出錯選項、優(yōu)化選項和體系結(jié)構(gòu)相關(guān)選項。以下對每一類中最常用的選項進行講解。
(1)常用選項。
gcc的常用選項如表3.7所示,很多在前面的示例中已經(jīng)有所涉及。
表3.7 gcc常用選項列表
選項 | 含義 |
-c | 只編譯不鏈接,生成目標(biāo)文件“.o” |
-S | 只編譯不匯編,生成匯編代碼 |
-E | 只進行預(yù)編譯,不做其他處理 |
-g | 在可執(zhí)行程序中包含標(biāo)準(zhǔn)調(diào)試信息 |
-ofile | 將file文件指定為輸出文件 |
-v | 打印出編譯器內(nèi)部編譯各過程的命令行信息和編譯器的版本 |
-Idir | 在頭文件的搜索路徑列表中添加dir目錄 |
前一小節(jié)已經(jīng)講解了“-c”、“-E”、“-o”、“-S”選項的使用方法,在此主要講解另外2個非常常用的庫依賴選項“-Idir”。
n “-Idir”
正如上表中所述,“-Idir”選項可以在頭文件的搜索路徑列表中添加dir目錄。由于Linux中頭文件都默認(rèn)放到了“/usr/include/”目錄下,因此,當(dāng)用戶希望添加放置在其他位置的頭文件時,就可以通過“-Idir”選項來指定,這樣,gcc就會到相應(yīng)的位置查找對應(yīng)的目錄。
比如在“/root/workplace/gcc”下有兩個文件:
/*hello1.c*/
#includemy.h>
intmain()
{
printf(Hello!!n);
return0;
}
/*my.h*/
#includestdio.h>
這樣,就可在gcc命令行中加入“-I”選項:
[root@localhostgcc]gcchello1.c–I/root/workplace/gcc/-ohello1
這樣,gcc就能夠執(zhí)行出正確結(jié)果。
小知識 | 在include語句中,“>”表示在標(biāo)準(zhǔn)路徑中搜索頭文件,““””表示在本目錄中搜索。故在上例中,可把hello1.c的“#includemy.h>”改為“#include“my.h””,就不需要加上“-I”選項了。 |
(2)庫選項。
gcc庫選項如表3.8所示。
表3.8 gcc庫選項列表
選項 | 含義 |
-static | 進行靜態(tài)編譯,即鏈接靜態(tài)庫,禁止使用動態(tài)庫 |
-shared | 1.可以生成動態(tài)庫文件 2.進行動態(tài)編譯,盡可能地鏈接動態(tài)庫,只有當(dāng)沒有動態(tài)庫時才會鏈接同名的靜態(tài)庫(默認(rèn)選項,即可省略) |
-Ldir | 在庫文件的搜索路徑列表中添加dir目錄 |
-lname | 鏈接稱為libname.a(靜態(tài)庫)或者libname.so(動態(tài)庫)的庫文件。若兩個庫都存在,則根據(jù)編譯方式(-static還是-shared)而進行鏈接 |
-fPIC(或-fpic) | 生成使用相對地址的位置無關(guān)的目標(biāo)代碼(PositionIndependentCode)。然后通常使用gcc的-static選項從該PIC目標(biāo)文件生成動態(tài)庫文件 |
我們通常需要將一些常用的公共函數(shù)編譯并集成到二進制文件(Linux的ELF格式文件),以便其他程序可重復(fù)地使用該文件中的函數(shù),此時將這種文件叫做函數(shù)庫,使用函數(shù)庫不僅能夠節(jié)省很多內(nèi)存和存儲器的空間資源,而且更重要的是大大降低開發(fā)難度和開銷,提高開發(fā)效率并增強程序的結(jié)構(gòu)性。實際上,在Linux中的每個程序都會鏈接到一個或者多個庫。比如使用C函數(shù)的程序會鏈接到C運行時庫,Qt應(yīng)用程序會鏈接到Qt支持的相關(guān)圖形庫等。
函數(shù)庫有靜態(tài)庫和動態(tài)庫兩種,靜態(tài)庫是一系列的目標(biāo)文件(.o文件)的歸檔文件(文件名格式為libname.a),如果在編譯某個程序時鏈接靜態(tài)庫,則鏈接器將會搜索靜態(tài)庫,從中提取出它所需要的目標(biāo)文件并直接復(fù)制到該程序的可執(zhí)行二進制文件(ELF格式文件)之中;動態(tài)庫(文件名格式為libname.so[.主版本號.次版本號.發(fā)行號])在程序編譯時并不會被鏈接到目標(biāo)代碼中,而是在程序運行時才被載入。
下面舉一個簡單的例子,講解如何怎么創(chuàng)建和使用這兩種函數(shù)庫。
首先創(chuàng)建unsgn_pow.c文件,它包含unsgn_pow()函數(shù)的定義,具體代碼如下所示。
/*unsgn_pow.c:庫程序*/
unsignedlonglongunsgn_pow(unsignedintx,unsignedinty)
{
unsignedlonglongres=1;
if(y==0)
{
res=1;
}
elseif(y==1)
{
res=x;
}
else
{
res=x*unsgn_pow(x,y-1);
}
returnres;
}
然后創(chuàng)建pow_test.c文件,它會調(diào)用unsgn_pow()函數(shù)。
/*pow_test.c*/
#includestdio.h>
#includestdlib.h>
intmain(intargc,char*argv[])
{
unsignedintx,y;
unsignedlonglongres;
if((argc3)||(sscanf(argv[1],%u,x)!=1)
||(sscanf(argv[2],%u,y))!=1)
{
printf(Usage:powbaseexponentn);
exit(1);
}
res=unsgn_pow(x,y);
printf(%u^%u=%un,x,y,res);
exit(0);
}
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)linux相關(guān)文章:linux教程
c++相關(guān)文章:c++教程
評論