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