c編譯器so easy,gcc c編譯器生成、使用動(dòng)靜態(tài)庫(kù)(中篇)
自學(xué)c編譯器的朋友都知道。c編譯器作為常用軟件之一,并非具備無(wú)法逾越難度。對(duì)于c編譯器的學(xué)習(xí),往往需要具備一定耐心。本文對(duì)c編譯器的講解基于gcc c編譯器,同時(shí)本文承接“c編譯器so easy,gcc c編譯器生成、使用動(dòng)靜態(tài)庫(kù)(上篇)”一文而談,不了解的朋友可以先回顧一番哦。此外,本文主要內(nèi)容為gcc生成靜態(tài)和動(dòng)態(tài)鏈接庫(kù)的示例,一起來(lái)了解下吧。
我們通常把一些公用函數(shù)制作成函數(shù)庫(kù),供其它程序使用。函數(shù)庫(kù)分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)兩種。
靜態(tài)庫(kù)在程序編譯時(shí)會(huì)被連接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要該靜態(tài)庫(kù)。
動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入,因此在程序運(yùn)行時(shí)還需要?jiǎng)討B(tài)庫(kù)存在。
本文主要通過(guò)舉例來(lái)說(shuō)明在Linux中如何創(chuàng)建靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù),以及使用它們。
為了便于闡述,我們先做一部分準(zhǔn)備工作。
1.準(zhǔn)備好測(cè)試代碼hello.h、hello.c和main.c
hello.h(見(jiàn)程序1)為該函數(shù)庫(kù)的頭文件。
hello.c(見(jiàn)程序2)是函數(shù)庫(kù)的源程序,其中包含公用函數(shù)hello,該函數(shù)將在屏幕上輸出“Hello XXX!”。
main.c(見(jiàn)程序3)為測(cè)試庫(kù)文件的主程序,在主程序中調(diào)用了公用函數(shù)hello。
程序1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
程序2:hello.c#include
void hello(const char *name) {
printf(“Hello %s!\n”, name);
}
程序3:main.c#include “hello.h”
int main()
{
hello(“everyone”);
return 0;
}
2.問(wèn)題的提出
注意:這個(gè)時(shí)候,我們編譯好的hello.o是無(wú)法通過(guò)gcc –o 編譯的,這個(gè)道理非常簡(jiǎn)單,
hello.c是一個(gè)沒(méi)有main函數(shù)的.c程序,因此不夠成一個(gè)完整的程序,如果使用gcc –o 編譯并連接它,GCC將報(bào)錯(cuò)。
無(wú)論靜態(tài)庫(kù),還是動(dòng)態(tài)庫(kù),都是由.o文件創(chuàng)建的。因此,我們必須將源程序hello.c通過(guò)gcc先編譯成.o文件。
這個(gè)時(shí)候我們有三種思路:
1) 通過(guò)編譯多個(gè)源文件,直接將目標(biāo)代碼合成一個(gè).o文件。
2) 通過(guò)創(chuàng)建靜態(tài)鏈接庫(kù)libmyhello.a,使得main函數(shù)調(diào)用hello函數(shù)時(shí)可調(diào)用靜態(tài)鏈接庫(kù)。
3) 通過(guò)創(chuàng)建動(dòng)態(tài)鏈接庫(kù)libmyhello.so,使得main函數(shù)調(diào)用hello函數(shù)時(shí)可調(diào)用靜態(tài)鏈接庫(kù)。
3.思路一:編譯多個(gè)源文件
在系統(tǒng)提示符下鍵入以下命令得到hello.o文件。
# gcc -c hello.c
為什么不使用gcc–o hello hello.cpp 這個(gè)道理我們之前已經(jīng)說(shuō)了,使用-c是什么意思呢?這涉及到gcc 編譯選項(xiàng)的常識(shí)。
我們通常使用的gcc –o 是將.c源文件編譯成為一個(gè)可執(zhí)行的二進(jìn)制代碼(-o選項(xiàng)其實(shí)是制定輸出文件文件名,如果不加-c選項(xiàng),gcc默認(rèn)會(huì)編譯連接生成可執(zhí)行文件,文件的名稱有-o選項(xiàng)指定),這包括調(diào)用作為GCC內(nèi)的一部分真正的C編譯器(ccl),以及調(diào)用GNU C編譯器的輸出中實(shí)際可執(zhí)行代碼的外部GNU匯編器(as)和連接器工具(ld)。
而gcc –c是使用GNU匯編器將源文件轉(zhuǎn)化為目標(biāo)代碼之后就結(jié)束,在這種情況下,只調(diào)用了C編譯器(ccl)和匯編器(as),而連接器(ld)并沒(méi)有被執(zhí)行,所以輸出的目標(biāo)文件不會(huì)包含作為L(zhǎng)inux程序在被裝載和執(zhí)行時(shí)所必須的包含信息,但它可以在以后被連接到一個(gè)程序。
我們運(yùn)行l(wèi)s命令看看是否生存了hello.o文件。
# ls
hello.c hello.h hello.o main.c
在ls命令結(jié)果中,我們看到了hello.o文件,本步操作完成。
同理編譯main
#gcc –c main.c
將兩個(gè)文件鏈接成一個(gè).o文件。
#gcc –o hello hello.o main.o
運(yùn)行
# 。/hello
Hello everyone!
4.思路二:靜態(tài)鏈接庫(kù)
下面我們先來(lái)看看如何創(chuàng)建靜態(tài)庫(kù),以及使用它。
靜態(tài)庫(kù)文件名的命名規(guī)范是以lib為前綴,緊接著跟靜態(tài)庫(kù)名,擴(kuò)展名為.a。例如:我們將創(chuàng)建的靜態(tài)庫(kù)名為myhello,則靜態(tài)庫(kù)文件名就是libmyhello.a。在創(chuàng)建和使用靜態(tài)庫(kù)時(shí),需要注意這點(diǎn)。創(chuàng)建靜態(tài)庫(kù)用ar命令。
在系統(tǒng)提示符下鍵入以下命令將創(chuàng)建靜態(tài)庫(kù)文件libmyhello.a。
# ar rcs libmyhello.a hello.o
我們同樣運(yùn)行l(wèi)s命令查看結(jié)果:
# ls
hello.c hello.h hello.o libmyhello.a main.c
ls命令結(jié)果中有l(wèi)ibmyhello.a。
靜態(tài)庫(kù)制作完了,如何使用它內(nèi)部的函數(shù)呢?只需要在使用到這些公用函數(shù)的源程序中包含這些公用函數(shù)的原型聲明,然后在用gcc命令生成目標(biāo)文件時(shí)指明靜態(tài)庫(kù)名,gcc將會(huì)從靜態(tài)庫(kù)中將公用函數(shù)連接到目標(biāo)文件中。注意,gcc會(huì)在靜態(tài)庫(kù)名前加上前綴lib,然后追加擴(kuò)展名.a得到的靜態(tài)庫(kù)文件名來(lái)查找靜態(tài)庫(kù)文件,因此,我們?cè)趯懶枰B接的庫(kù)時(shí),只寫名字就可以,如libmyhello.a的庫(kù),只寫:-lmyhello
在程序3:main.c中,我們包含了靜態(tài)庫(kù)的頭文件hello.h,然后在主程序main中直接調(diào)用公用函數(shù)hello。下面先生成目標(biāo)程序hello,然后運(yùn)行hello程序看看結(jié)果如何。
# gcc -o hello main.c -static -L. -lmyhello
# 。/hello
Hello everyone!
我們刪除靜態(tài)庫(kù)文件試試公用函數(shù)hello是否真的連接到目標(biāo)文件 hello中了。
# rm libmyhello.a
rm: remove regular file `libmyhello.a‘? y
# 。/hello
Hello everyone!
程序照常運(yùn)行,靜態(tài)庫(kù)中的公用函數(shù)已經(jīng)連接到目標(biāo)文件中了。
靜態(tài)鏈接庫(kù)的一個(gè)缺點(diǎn)是,如果我們同時(shí)運(yùn)行了許多程序,并且它們使用了同一個(gè)庫(kù)函數(shù),這樣,在內(nèi)存中會(huì)大量拷貝同一庫(kù)函數(shù)。這樣,就會(huì)浪費(fèi)很多珍貴的內(nèi)存和存儲(chǔ)空間。使用了共享鏈接庫(kù)的Linux就可以避免這個(gè)問(wèn)題。
共享函數(shù)庫(kù)和靜態(tài)函數(shù)在同一個(gè)地方,只是后綴有所不同。比如,在一個(gè)典型的Linux系統(tǒng),標(biāo)準(zhǔn)的共享數(shù)序函數(shù)庫(kù)是/usr/lib/libm.so。
當(dāng)一個(gè)程序使用共享函數(shù)庫(kù)時(shí),在連接階段并不把函數(shù)代碼連接進(jìn)來(lái),而只是鏈接函數(shù)的一個(gè)引用。當(dāng)最終的函數(shù)導(dǎo)入內(nèi)存開始真正執(zhí)行時(shí),函數(shù)引用被解析,共享函數(shù)庫(kù)的代碼才真正導(dǎo)入到內(nèi)存中。這樣,共享鏈接庫(kù)的函數(shù)就可以被許多程序同時(shí)共享,并且只需存儲(chǔ)一次就可以了。共享函數(shù)庫(kù)的另一個(gè)優(yōu)點(diǎn)是,它可以獨(dú)立更新,與調(diào)用它的函數(shù)毫不影響。
5.思路三、動(dòng)態(tài)鏈接庫(kù)(共享函數(shù)庫(kù))
我們繼續(xù)看看如何在Linux中創(chuàng)建動(dòng)態(tài)庫(kù)。我們還是從.o文件開始。
動(dòng)態(tài)庫(kù)文件名命名規(guī)范和靜態(tài)庫(kù)文件名命名規(guī)范類似,也是在動(dòng)態(tài)庫(kù)名增加前綴lib,但其文件擴(kuò)展名為.so。例如:我們將創(chuàng)建的動(dòng)態(tài)庫(kù)名為myhello,則動(dòng)態(tài)庫(kù)文件名就是libmyhello.so。用gcc來(lái)創(chuàng)建動(dòng)態(tài)庫(kù)。
在系統(tǒng)提示符下鍵入以下命令得到動(dòng)態(tài)庫(kù)文件libmyhello.so。
# gcc -shared -fPIC -o libmyhello.so hello.o
“PIC”命令行標(biāo)記告訴GCC產(chǎn)生的代碼不要包含對(duì)函數(shù)和變量具體內(nèi)存位置的引用,這是因?yàn)楝F(xiàn)在還無(wú)法知道使用該消息代碼的應(yīng)用程序會(huì)將它連接到哪一段內(nèi)存地址空間。這樣編譯出的hello.o可以被用于建立共享鏈接庫(kù)。建立共享鏈接庫(kù)只需要用GCC的”-shared”標(biāo)記即可。
我們照樣使用ls命令看看動(dòng)態(tài)庫(kù)文件是否生成。
# ls
hello.cpp hello.h hello.o libmyhello.so main.cpp
調(diào)用動(dòng)態(tài)鏈接庫(kù)編譯目標(biāo)文件。
在程序中使用動(dòng)態(tài)庫(kù)和使用靜態(tài)庫(kù)完全一樣,也是在使用到這些公用函數(shù)的源程序中包含這些公用函數(shù)的原型聲明,然后在用gcc命令生成目標(biāo)文件時(shí)指明動(dòng)態(tài)庫(kù)名進(jìn)行編譯。我們先運(yùn)行g(shù)cc命令生成目標(biāo)文件,再運(yùn)行它看看結(jié)果。
如果直接用如下方法進(jìn)行編譯,并連接:
# gcc -o hello main.c -L. -lmyhello
(使用”-lmyhello”標(biāo)記來(lái)告訴GCC驅(qū)動(dòng)程序在連接階段引用共享函數(shù)庫(kù)libmyhello.so。”-L.”標(biāo)記告訴GCC函數(shù)庫(kù)可能位于當(dāng)前目錄。否則GNU連接器會(huì)查找標(biāo)準(zhǔn)系統(tǒng)函數(shù)目錄:它先后搜索1.elf文件的 DT_RPATH段—2.環(huán)境變量LD_LIBRARY_PATH—3./etc/ld.so.cache文件列表—4./lib/,/usr/lib目錄找到庫(kù)文件后將其載入內(nèi)存,但是我們生成的共享庫(kù)在當(dāng)前文件夾下,并沒(méi)有加到上述的4個(gè)路徑的任何一個(gè)中,因此,執(zhí)行后會(huì)出現(xiàn)錯(cuò)誤)
# 。/hello
。/hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
#
錯(cuò)誤提示,找不到動(dòng)態(tài)庫(kù)文件libmyhello.so。程序在運(yùn)行時(shí),會(huì)在/usr/lib和/lib等目錄中查找需要的動(dòng)態(tài)庫(kù)文件。若找到,則載入動(dòng)態(tài)庫(kù),否則將提示類似上述錯(cuò)誤而終止程序運(yùn)行。有多種方法可以解決,
(1)我們將文件 libmyhello.so復(fù)制到目錄/usr/lib中,再試試。
# mv libmyhello.so /usr/lib
# 。/hello
成功!
(2)既然連接器會(huì)搜尋LD_LIBRARY_PATH所指定的目錄,那么我們可以將這個(gè)環(huán)境變量設(shè)置成當(dāng)前目錄:
先執(zhí)行:
export LD_LIBRARY_PATH=$(pwd)
再執(zhí)行:
。/hello
成功!
(3)執(zhí)行:
ldconfig /usr/zhsoft/lib
注: 當(dāng)用戶在某個(gè)目錄下面創(chuàng)建或拷貝了一個(gè)動(dòng)態(tài)鏈接庫(kù),若想使其被系統(tǒng)共享,可以執(zhí)行一下“ldconfig 目錄名”這個(gè)命令。此命令的功能在于讓ldconfig將指定目錄下的動(dòng)態(tài)鏈接庫(kù)被系統(tǒng)共享起來(lái),意即:在緩存文件/etc/ld.so.cache中追加進(jìn)指定目錄下的共享庫(kù)。本例讓系統(tǒng)共享了/usr/zhsoft/lib目錄下的動(dòng)態(tài)鏈接庫(kù)。該命令會(huì)重建/etc/ld.so.cache文件。
以上便是此次小編帶來(lái)的“c編譯器”相關(guān)內(nèi)容,通過(guò)本文,希望大家對(duì)gcc生成靜態(tài)和動(dòng)態(tài)鏈接庫(kù)的過(guò)程具備一定的了解。如果你喜歡本文,不妨持續(xù)關(guān)注我們網(wǎng)站哦,小編將于后期帶來(lái)更多精彩內(nèi)容。最后,十分感謝大家的閱讀,have a nice day!