在說明Linux的.a、.so和.o文件關系之前,先來看看windows下obj,lib,dll,exe的關系
windows下obj,lib,dll,exe的關系
lib是和dll對應的。lib是靜態鏈接庫的庫文件,dll是動態鏈接庫的庫文件。
所謂靜態就是link的時候把里面需要的東西抽取出來安排到你的exe文件中,以后運行你的exe的時候不再需要lib。
所謂動態就是exe運行的時候依賴於dll里面提供的功能,沒有這個dll,你的exe無法運行。
lib,dll,exe都算是最終的目標文件,是最終產物。而c/c++屬於源代碼。源代碼和最終目標文件中過渡的就是中間代碼obj,實際上之所以需要中間代碼,是你不可能一次得到目標文件。比如說一個exe需要很多的cpp文件生成。而編譯器一次只能編譯一個cpp文件。這樣編譯器編譯好一個cpp以后會將其編譯成obj,當所有必須要的cpp都編譯成obj以后,再統一link成所需要的exe,應該說缺少任意一個obj都會導致exe的鏈接失敗。
1.obj里存的是編譯后的代碼跟數據,並且有名稱,所以在連接時有時會出現未解決的外部符號的問題。當連成exe后便不存在名稱的概念了,只有地址。lib就是一堆obj的組合。
2.理論上可以連接obj文件來引用其他工程(可以認為一個obj文件等價於編譯生成它的cpp文件,可以引用obj來替換cpp,也可以添加cpp來替換obj ),但實際中通常用lib來實現工程間相互引用。
3.編譯器會默認鏈接一些常用的庫,其它的需要你自己指定。
lib和DLL的區別
(1)lib是編譯時需要的,dll是運行時需要的。如果要完成源代碼的編譯,有lib就夠了。如果也使動態連接的程序運行起來,有dll就夠了。在開發和調試階段,當然最好都有。
(2) 一般的動態庫程序有lib文件和dll文件。lib文件是必須在編譯期就連接到應用程序中的,而dll文件是運行期才會被調用的。如果有dll文件,那么對應的lib文件一般是一些索引信息,具體的實現在dll文件中。如果只有lib文件,那么這個lib文件是靜態編譯出來的,索引和實現都在其中。 靜態編譯的lib文件有好處:給用戶安裝時就不需要再掛動態庫了。但也有缺點,就是導致應用程序比較大,而且失去了動態庫的靈活性,在版本升級時,同時要發布新的應用程序才行。
(3)在動態庫的情況下,有兩個文件,一個是引入庫(.LIB)文件(實際上也算是一個靜態庫,只是在鏈接時只能把函數在DLL的入口鏈接到exe中,而不像真正靜態鏈接庫那樣將函數體真正鏈接到exe中 ,通過lib進行的動態鏈接實際上也使用了靜態鏈接來實現 ),一個是DLL文件,引入庫文件包含被DLL導出的函數的名稱和位置,DLL包含實際的函數和數據,應用程序使用LIB文件鏈接到所需要使用的DLL文件,庫中的函數和數據並不復制到可執行文件中,因此在應用程序的可執行文件中,存放的不是被調用的函數代碼,而是DLL中所要調用的函數的內存地址,這樣當一個或多個應用程序運行是再把程序代碼和被調用的函數代碼鏈接起來,從而節省了內存資源。從上面的說明可以看出,DLL和.LIB文件必須隨應用程序一起發行,否則應用程序將會產生錯誤。
DLL內的函數分為兩種:
(1)DLL導出函數,可供應用程序調用;
(2)DLL內部函數,只能在DLL程序使用,應用程序無法調用它們
創建靜態鏈接庫和創建動態鏈接庫
VC6中創建[Win32 Dynamic-Link Library]工程便可以創建出一個空的DLL工程.
VC6中創建[Win32 Static Library]工程便可以創建出一個空的LIB工程(靜態鏈接庫工程,僅生成一個lib文件).
添加lib文件的常用辦法有二個:
1、把*.lib放在VC的Lib目錄中
2、修改project setting的Link->Input中的Addtional library path,加入你的目錄dll:是可實際運行的二進制代碼,有定位代碼的!
3、也可以在object/library中直接寫上lib文件路徑.(這里實際上是可以寫上任意obj文件或者lib文件的).
linux .o,.a,.so
.o,是目標文件,相當於windows中的.obj文件
.so 為共享庫,是shared object,用於動態連接的,相當於windows下的dll
.a為靜態庫,是好多個.o合在一起,用於靜態連接
靜態函數庫
特點:實際上是簡單的普通目標文件的集合,在程序執行前就加入到目標程序中。
優點:可以用以前某些程序兼容;描述簡單;允許程序員把程序link起來而不用重新編譯代碼,節省了重新編譯代碼的時間(該優勢目前已不明顯);開發者可以對源代碼保密;理論上使用ELF格式的靜態庫函數生成的代碼可以比使用共享或動態函數庫的程序運行速度快(大概1%-5%)
生成:使用ar程序(archiver的縮寫)。ar rcs my_lib.a f1.o f2.o是把目標代碼f1.o和f2.o加入到my_lib.a這個函數庫文件中(如果my_lib.a不存在則創建)
使用:用gcc生成可執行代碼時,使用-l參數指定要加入的庫函數。也可以用ld命令的-l和-L參數。
共享函數庫
共享函數庫在可執行程序啟動的時候加載,所有程序重新運行時都可自動加載共享函數庫中的函數。.so文件感覺很復雜,光是命名規則就已經看得我很暈了~整理一下,共享庫需要:soname、real name,另外編譯的時候名字也有說法。依次解釋下:
soname:必須的格式:lib+函數庫名+.so+版本號信息(但是記住,非常底層的C庫函數都不是以lib開頭命名的)。例子:/usr/lib/libreadline.so.3
real name:顧名思義是真正的名字啦,有主版本號和發行版本號。但是沒找到實例……
編譯器編譯的時候需要的函數庫的名字就是不包含版本號信息的soname,例如上面的例子把最后的.3去掉就可以了。
位置:共享函數庫文件必須放在特定目錄,對於開放源碼來說,GNU標准建議所有的函數庫文件都放在/usr/local/lib目錄下,而且建議命令、可執行程序都放在/usr/local/bin目錄下。不過這個只是習慣啦,可以改變,具體的位置信息可以看/etc/ld.so.conf里面的配置信息。當然,也可以修改這個文件,加入自己的一些特殊的路徑要求。
創建:在網上找到了gcc方式和easyeclipse環境下兩種創建方式。
gcc方式:
首先創建object文件,這個文件將加入通過gcc –fPIC 參數命令加入到共享函數庫里面,標准格式:gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list(說實話這個標准格式看起來好復雜,我找了個實例,但是好像和那個標准格式稍有不同:gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so)
在easyeclipse環境下生成.so文件:
1.選擇新建工程,建立一個c++工程
2.在工程類型選項里選擇 Shared Library,然后填入工程名字PXXX點擊完成即可。
3.編寫程序,然后編譯就會在debug或者release里生成一個libPXXX.so文件,如果不要lib的起頭標記點擊project菜單的Properties選項,然后在彈出的界面的右邊點擊Build artifact頁面,將Output prefix選項的內容清空即可。
4.如果是C++程序,注意在接口函數的前面加上extern "C"標記,在頭文件加上如下標記:
#ifdef __cplusplus
#extern "C"{
#endif
頭文件主體
#ifdef __cplusplus
}
#endif
如果不加以上標記,經過編譯后,so里的函數名並非你編寫程序時設定的函數名,在開發環境左側的工程文件列表中點開debug項里的PXXX.o可以看到so文件里的函數名都是在你設定的函數名后面加了一個__Fi標記,比如你用的設定的函數名稱是Func(), 而so里的函數名則為Func__Fi()或者其他的名稱。
安裝:拷貝共享庫文件到指定的標准的目錄,然后運行ldconfig。如果沒有權限這樣做,那么就只好通過修改環境變量來實現這些函數庫的使用了。方法不再說了,很復雜。
查看:可以通過運行ldd來看某個程序使用的共享函數庫。例如ldd /bin/ls。查看.so文件使用nm命令,如nm libXXX.so。(注意,nm對於靜態的函數庫和共享的函數庫都起作用)
關於覆蓋:如果想用自己的函數覆蓋某個庫中的一些函數,同時保留該庫中其他的函數的話,可以在/etc/ld.so.preload中加入要替換的庫(.o結尾的文件),這些preloading的庫函數將有優先加載的權利。
關於更新:每次新增加動態加載的函數庫、刪除某個函數庫或者修改某個函數庫的路徑時,都要重新運行ldconfig來更新緩存文件/etc/ld.so.cache,此文件保存已排好序的動態鏈接庫名字列表
(在Linux下,共享庫的加載是由/lib/ld.so完成的,ld.so加載共享庫時,會從ld.so.cache查找)
我們通常把一些公用函數制作成函數庫,供其它程序使用。函數庫分為靜態庫和動態庫兩
種。靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。動態
庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運
行時還需要動態庫存在。本文主要通過舉例來說明在Linux中如何創建靜態庫和動態庫,以
及使用它們。
在創建函數庫前,我們先來准備舉例用的源程序,並將函數庫的源程序編譯成.o文件。
第1步:編輯得到舉例的程序--hello.h、hello.c和main.c;
hello.c(見程序2)是函數庫的源程序,其中包含公用函數hello,該函數將在屏幕上輸出"
Hello XXX!"。hello.h(見程序1)為該函數庫的頭文件。main.c(見程序3)為測試庫文件的
主程序,在主程序中調用了公用函數hello。
程序1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程序2: hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
程序3: main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
第2步:將hello.c編譯成.o文件;
無論靜態庫,還是動態庫,都是由.o文件創建的。因此,我們必須將源程序hello.c通過g
cc先編譯成.o文件。
在系統提示符下鍵入以下命令得到hello.o文件。
# gcc -c hello.c
#
我們運行ls命令看看是否生存了hello.o文件。
# ls
hello.c hello.h hello.o main.c
#
在ls命令結果中,我們看到了hello.o文件,本步操作完成。
下面我們先來看看如何創建靜態庫,以及使用它。
第3步:由.o文件創建靜態庫;
靜態庫文件名的命名規范是以lib為前綴,緊接着跟靜態庫名,擴展名為.a。例如:我們將
創建的靜態庫名為myhello,則靜態庫文件名就是libmyhello.a。在創建和使用靜態庫時,
需要注意這點。創建靜態庫用ar命令。
在系統提示符下鍵入以下命令將創建靜態庫文件libmyhello.a。
# ar -cr libmyhello.a hello.o
#
我們同樣運行ls命令查看結果:
# ls
hello.c hello.h hello.o libmyhello.a main.c
#
ls命令結果中有libmyhello.a。
第4步:在程序中使用靜態庫;
靜態庫制作完了,如何使用它內部的函數呢?只需要在使用到這些公用函數的源程序中包
含這些公用函數的原型聲明,然后在用gcc命令生成目標文件時指明靜態庫名,gcc將會從
靜態庫中將公用函數連接到目標文件中。注意,gcc會在靜態庫名前加上前綴lib,然后追
加擴展名.a得到的靜態庫文件名來查找靜態庫文件。
在程序3:main.c中,我們包含了靜態庫的頭文件hello.h,然后在主程序main中直接調用公
用函數hello。下面先生成目標程序hello,然后運行hello程序看看結果如何。
法一 # gcc -o hello main.c -L. –lmyhello,或gcc main.c -L. –lmyhello -o hello自定義的庫時,main.c還可放在-L.和 –lmyhello之間,但是不能放在它倆之后,否則會提示myhello沒定義,但是是系統的庫時,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出錯。
法二 #gcc main.c libmyhello.a -o hello或gcc -o hello main.c libmyhello.a
法三:先生成main.o:gcc -c main.c ,再生成可執行文件:gcc -o hello main.o libmyhello.a或gccmain.o libmyhello.a -o hello ,動態庫連接時也可以這樣做。
# ./hello
Hello everyone!
#
我們刪除靜態庫文件試試公用函數hello是否真的連接到目標文件 hello中了。
# rm libmyhello.a
rm: remove regular file `libmyhello.a'? y
# ./hello
Hello everyone!
#
程序照常運行,靜態庫中的公用函數已經連接到目標文件中了。
我們繼續看看如何在Linux中創建動態庫。我們還是從.o文件開始。
第5步:由.o文件創建動態庫文件;
動態庫文件名命名規范和靜態庫文件名命名規范類似,也是在動態庫名增加前綴lib,但其
文件擴展名為.so。例如:我們將創建的動態庫名為myhello,則動態庫文件名就是libmyh
ello.so。用gcc來創建動態庫。
在系統提示符下鍵入以下命令得到動態庫文件libmyhello.so。
# gcc -shared -fPIC -o libmyhello.so hello.o (-o不可少)
#
我們照樣使用ls命令看看動態庫文件是否生成。
# ls
hello.c hello.h hello.o libmyhello.so main.c
#
第6步:在程序中使用動態庫;
在程序中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函數的源程序中包含
這些公用函數的原型聲明,然后在用gcc命令生成目標文件時指明動態庫名進行編譯。我們
先運行gcc命令生成目標文件,再運行它看看結果。
# gcc -o hello main.c -L. -lmyhello
(或 #gcc main.c libmyhello.so -o hello 不會出錯(沒有libmyhello.so的話,會出錯),但是接下來./hello 會提示出錯,因為雖然連接時用的是當前目錄的動態庫,但是運行時,是到/usr/lib中找庫文件的,將文件libmyhello.so復制到目錄/usr/lib中就OK了)
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
哦!出錯了。快看看錯誤提示,原來是找不到動態庫文件libmyhello.so。程序在運行時,
會在/usr/lib和/lib等目錄中查找需要的動態庫文件。若找到,則載入動態庫,否則將提
示類似上述錯誤而終止程序運行。我們將文件libmyhello.so復制到目錄/usr/lib中,再試
試。
# mv libmyhello.so /usr/lib
# ./hello
Hello everyone!
#
成功了。這也進一步說明了動態庫在程序運行時是需要的。
我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令完全一樣,
那當靜態庫和動態庫同名時,gcc命令會使用哪個庫文件呢?抱着對問題必究到底的心情,
來試試看。
先刪除除.c和.h外的所有文件,恢復成我們剛剛編輯完舉例程序狀態。
# rm -f hello hello.o /usr/lib/libmyhello.so
# ls
hello.c hello.h main.c
#
在來創建靜態庫文件libmyhello.a和動態庫文件libmyhello.so。
在生成動態庫時,需要使用-fPIC,這樣才能生成位置無關的代碼,達到代碼段和數據段共享的目的
# gcc -c -fpic hello.c //編譯hello.c時也需要加上-fpic選項,否則rodata' can not be used when making a shared object; recompile with -fPIC
# ar -cr libmyhello.a hello.o (或-cvr )
# gcc -shared -fPIC -o libmyhello.so hello.o
# ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
#
通過上述最后一條ls命令,可以發現靜態庫文件libmyhello.a和動態庫文件libmyhello.s
o都已經生成,並都在當前目錄中。然后,我們運行gcc命令來使用函數庫myhello生成目標
文件hello,並運行程序 hello。
# gcc -o hello main.c -L. –lmyhello (動態庫和靜態庫同時存在時,優先使用動態庫, 當然,直接#gcc main.c libmyhello.a -o hello的話,就是指定為靜態庫了)
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
從程序hello運行的結果中很容易知道,當靜態庫和動態庫同名時,gcc命令將優先使用動態庫,默認去連/usr/lib和/lib等目錄中的動態庫,將文件libmyhello.so復制到目錄/usr/lib中即可。
Note:
編譯參數解析
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code)。那么在產生的代碼中,沒有絕對地址,全部使用相對地址,故而代碼可以被加載器加載到內存的任意位置,都可以正確的執行。這正是共享庫所要求的,共享庫被加載時,在內存的位置不是固定的。
如果不加fPIC,則編譯出來的代碼在加載時需要根據加載到的位置進行重定位(因為它里面的代碼並不是位置無關代碼),如果被多個應用程序共同使用,那么它們必須每個程序維護一份so的代碼副本了.(因為so被每個程序加載的位置都不同,顯然這些重定位后的代碼也不同,當然不能共享)。
不用此選項的話編譯后的代碼是位置相關的,所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
-L. 表示要連接的庫在當前目錄中;(多個庫:在編譯命令行中,將使用的靜態庫文件放在源文件后面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib指定庫文件的查找路徑。編譯器默認在當前目錄下先查找指定的庫文件,如前面的“法二 #gccmain.c libmyhello.a-o hello”)
-lmyhello 編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so或.a來確定庫的名稱libmyhello.so或libmyhello.a。
LD_LIBRARY_PATH這個環境變量指示動態連接器可以裝載動態庫的路徑。
當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那么只能采用輸出LD_LIBRARY_PATH的方法了。
調用動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過 “-L”參數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
靜態庫鏈接時搜索路徑順序:
1. ld(GNU linker)會去找GCC命令中的參數-L
編譯過程是分為四個階段:預處理(也稱預編譯,Preprocessing)、編譯(Compilation)、匯編 (Assembly)和連接(link) 【鏈接】
2. 再找gcc的環境變量LIBRARY_PATH
3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的
動態鏈接時、執行時搜索路徑順序:
1. 編譯目標代碼時指定的動態庫搜索路徑
2. 環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑
3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑
4. 默認的動態庫搜索路徑/lib
5. 默認的動態庫搜索路徑/usr/lib
有關環境變量:
LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑
LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑
另:
從上述可知,如何找到生成的動態庫有3種方式:
(1)把庫拷貝到/usr/lib和/lib目錄下。
(2)在LD_LIBRARY_PATH環境變量中加上庫所在路徑。
例如動態庫libhello.so在/home/example/lib目錄下:
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf文件,把庫所在的路徑加到文件末尾(直接寫在文件末尾,不要在路徑前加include),並執行ldconfig刷新(ldconfig 命令的用途,主要是在默認搜尋目錄(/lib和/usr/lib)以及動態庫配置文件/etc/ld.so.conf內所列的目錄下,搜索出可共享的動態鏈接庫(格式如前介紹,lib*.so*),進而創建出動態裝入程序(ld.so)所需的連接和緩存文件.緩存文件默認為/etc/ld.so.cache,此文件保存已排好序的動態鏈接庫名字列表.)。這樣,加入的目錄下的所有庫文件都可見。
附:像下面這樣指定路徑去連接系統的靜態庫,會報錯說要連接的庫找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必須這樣g++ -o main main.cpp -L/usr/lib -lpthread才正確 。
自定義的庫考到/usr/lib 下時,
g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a會出錯,但是這樣g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass就正確了。
轉自 http://blog.csdn.net/chlele0105/article/details/23691147