一、簡介
實際開發工程中,一般會有很多函數只是聲明,而找不到實現的代碼,因為那些實現代碼已經編譯成庫了。在Linux系統中.o、.a、*.so文件都是Linux下的程序函數庫,即編譯好的可供其他程序使用的代碼和數據,一般來講:
- ==.o==:是目標文件,相當于windows中的.obj文件(動態加載函數庫);
- ==.so==:為共享庫,是shared object,用于動態鏈接的,和dill差不多(共享函數庫);
- ==.a==:為靜態庫,是好多個.o合在一起,用于靜態鏈接(靜態函數庫);
- ==.la==:為libtool自動生成的一些共享庫,vi編輯查看,主要記錄了一些配置信息;
這樣做有以下優點:程序模塊化,容易重新編譯,方便升級。
庫文件在鏈接(靜態庫和共享庫)和運行(僅限于使用共享庫的程序)時被使用,其搜索路勁是系統中進行設置的,一般Linux把/lib和/usr/lib兩個目錄作為默認的庫搜索路徑,所以使用這兩個目錄中的庫時,不需要進行設置搜索路徑即可直接使用。
假設當前目錄下有這些源文件:main.c func.c func.h,其中main.c要調用func.c中的函數。以下分別是Linux下將func.c生成靜態庫和動態庫的方法,以及如何使用生成的靜態庫和動態庫。
二、靜態鏈接庫
2.1 靜態庫的特點
靜態鏈接庫可以將代碼進行封裝,具有如下特點:
- 靜態函數庫實際上是簡單的普通目標文件(*.o)的集合;
- 靜態函數庫在可執行程序鏈接時就加入到可執行代碼中,在物理上成為可執行程序的一部分;
- 靜態函數庫鏈接生成的程序,在哪里都可以運行,無需該靜態函數庫的支持;
- 相對于動態函數庫鏈接生成的程序,靜態函數庫鏈接生成的可執行程序文件較大;
2.2 靜態庫的優點
相比源代碼來說,靜態鏈接庫有如下特點:
- 可以重用以前的程序模塊;
- 開發者可以對源代碼保密;
- 允許程序員不用重新編譯代碼而直接把程序link起來,節省了重新編譯代碼的時間(該優勢目前已不明顯);
- 理論上將,使用elf格式的靜態庫函數生成的代碼可以比使用共享函數庫生成的程序運行速度快。
2.3 靜態庫的制作
【命令格式】:
在linux環境中,使用ar命令創建靜態庫文件(創建嵌入式靜態庫,可使用arm-linux-ar命令),該命令格式如下:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
arm-linux-ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
- ==archive==:定義庫的名稱;
- ==files== :是庫文件中包含的目標文件的清單,用空格分隔每個文件;
【命令選項】:
- -a :把新的目標文件(*.o)添加到靜態庫文件中現有文件之后;
- -b :把新的目標文件(*.o)添加到靜態庫文件中現有文件之前;
- -d :從指定的靜態庫文件中刪除文件;
- -m:把文件移動到指定的靜態庫文件中;
- -p :把靜態庫文件中指定的文件輸出到標準輸出;
- -q :快速地把文件追加到靜態庫文件中;
- -r :把文件插入到靜態庫文件中;
- -t :顯示靜態庫文件中文件的列表;
- -x :從靜態庫文件中提取文件;
- -v :使用詳細模式
如下面創建了一個libapue.a庫文件,然后可以用t選項顯示包含在庫中的文件:
$ ar –r libapue.a error.o errorlog.o lockreg.o
++創建庫文件之后,可以創建這個靜態庫文件的索引來幫助提高和庫連接的其他程序的編譯速度。使用ranlib程序創建庫的索引,索引存放在庫文件的內部。++
$ ranlib libapue.a
++用nm程序可以顯示存檔文件的索引,它也可以顯示目標文件的符號:++
$ nm libapue.a | more
$ nm error.o | more
2.4 靜態庫的使用
靜態庫的使用方法,可以采用如下三種中的一種(-static可以不用):
$ gcc test.c –o test libapue.a
$ gcc test.c –o –L. -lfunc –static
$ gcc test.c –o –L./ -lfunc -static
【使用說明】
- 使用靜態庫生成的可執行文件放在目標板中可以直接運行;
- 使用靜態庫時,需用-L指定靜態庫的位置,如上面指定為當前目錄:-L./;
- 使用靜態庫時,需用-l指定靜態庫的庫名,如上面的-lfunc。
【完整使用實例】:
$ gcc func.c –c –o func.o //生成目標文件
$ ar crv libfunc.a func.o //生成靜態庫
$ gcc main.c –static –L./ –lfunc –o main //使用靜態庫
$ ./main //執行文件
三、動態鏈接庫
3.1 動態庫的簡介
動態庫是在可執行程序啟動時加載到執行程序中,可以被多個執行程序共享使用,使用動態庫編譯生成的程序相對較小,但運行時需要庫文件支持;
動態庫的鏈接時路徑和運行時路徑:現代鏈接器在處理動態庫時將鏈接時路徑(Link-time path)和運行時路徑(Run-time path)分開,用戶可通過-L指定鏈接時,庫文件的路徑;通過-R(或-rpath)指定程序運行時,庫文件的路徑,大大提高了庫應用的靈活性。比如我們做嵌入式移植時#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉編譯好的zlib庫),將target編譯好后我們只要把zlib庫拷貝到開發板的系統默認路徑下即可。或者通過- rpath(或-R )、LD_LIBRARY_PATH指定查找路徑。
3.2 動態庫的命名
每個動態庫(*.so)文件都有個特殊的名字,叫做“soname”,它必須以“lib”作為前綴,然后是函數庫名,然后是“.so”,最后是版本號信息;不過有個特例,就是非常底層的C庫函數都不是以lib開頭命名的;每個動態庫(*.so)文件都有一個真正的名字,叫做“real name”,它是包含真正庫函數代碼的文件;真名有一個主版本號和一個發行版本號外,還有一個名字是編譯器編譯的時候需要的函數庫的名字,這個就是簡單的soname名字,它不包含任何版本號信息:
- soname:必須的格式:lib+函數庫名+.so+版本號信息,如libreadline.so.3
- real name:也就是真正的名字,有主版本號和發行版本號;
- 編譯名字:編譯器編譯的時候需要的函數庫的名字就是不包含版本號信息的soname,如上面的libreadlie.so.3去掉后面的“.3”即可。
管理共享函數庫的關鍵是區分好這些名字,++當可執行程序需要在自己的程序中列出這些他們需要的共享庫函數的時候,它只要用soname就可以了;反過來,當要創建一個新的共享函數庫的時候,只要指定一個特定的文件名,其中包含很細節的版本信息;當安裝一個新版本的函數庫的時候,只要先將這些函數庫文件拷貝到一些特定的目錄中,運行ldconfig這個命令即可++(ldconfig檢查已存在的庫文件,然后創建soname的符號鏈接到真正的函數庫,同時設置/etc/ld.so.cache這個緩沖文件)。
3.3 動態庫的制作
由于動態鏈接庫的共享特性,它們不會被拷貝到可執行文件中。在編譯的時候,編譯器只會做一些函數名之類的檢查,在程序運行的時候,被調用的動態鏈接庫函數被安置在內存的某個地方,所有調用它的程序將指向這個代碼段。因此,這些代碼必須使用相對地址,而不是絕對地址。那么在編譯的時候,我們需要使用地址無關選項(Position Independent Code:PIC)“-fpic”來告訴編譯器,這些對象文件是用來做動態鏈接庫的。創建共享庫的方法:
$ gcc -fpic error.c -c -o error.o
$ gcc -fpic errorlog.c -c -o errorlog.o
$ gcc -fpic lockreg.c -c -o lockreg.o
$ gcc -shared -o libapue.so error.o errorlog.o lockreg.o
也可以使用如下方法:
$ gcc -shared -fpic error.c errorlog.c lockreg.c -o libapue.so
- -shared選項是告訴編譯器這是要建立動態鏈接庫;這與靜態鏈接庫的建立很不一樣,這里使用的gcc命令而不是ar,同時庫的后綴名為*.so;
- -fpic選項告訴編譯器該代碼為位置無關碼,不用此選項的話編譯后的代碼是位置相關的,所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的;
編譯共享庫的方法(假設庫文件在當前目錄),可采用如下三種中的一種:
$ gcc test.c -o test -L. -lapue
$ gcc test.c -o test -L./ -lapue
$ gcc test.c -o test libapue.so
這樣就編譯出了不包含函數代碼的可執行文件了,但是當你運行生成的可執行文件時,動態加載器無法動態加載libapue.so文件。那如何才能讓動態加載器發現庫文件呢?有如下兩種方法:
- 在環境變量LD_LIBRARY_PATH中指明庫的搜索路徑;
export LD_LIBRARY_PATH=”$(LD_LIBRARY_PATH):.” #添加當前路勁
- 配置/etc/ld.so.conf文件(推薦且簡單的方法):一般應用程序的庫文件不與系統庫文件放在同一個目錄下,一般把應用程序的共享庫文件放在/usr/local/lib下,新建一個屬于自己的目錄apue,然后把自己的libapue.so復制過去就行了;同時在/etc/ld.so.conf中新增一行:
/usr/local/lib/apue
以后在編譯程序時加上編譯選項:-L/usr/local/lib/apue –lapue;針對具體的可執行文件,可以通過ldd命令查看該可執行文件依賴什么共享庫:
ldd test
3.4 動態庫的使用
【使用說明】
- 使用動態庫生成的可執行文件,需將生成的動態庫也拷貝到目標板的連接文件目錄(/lib或/usr/lib)中,才可以順利執行。如果將生成的libfunc.so.1.0.0拷貝到系統lib目錄(如/usr/lib),則倒數第二步就不需要了。
- 使用動態庫鏈接生成可執行文件時,需要用-L指定動態庫的位置,如上面指定為當前目錄:-L./。
【使用實例】
$ gcc –fPIC func.c -c –o func.o
$ gcc –shared –o libfunc.so.1.0.0 func.o
$ ln –s libfunc.so.1.0.0 libfunc.so
$ gcc main.c -o main -lfunc -L./
$ export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
$ ./main
四、程序編譯時和運行時庫文件路徑的指定方法
如何指定程序編譯和運行時使用的靜態庫和動態庫呢?在連接時,可以通過-L選項來指定編譯時所用到的靜態庫和動態庫文件路徑。在程序運行時,可以通過配置/etc/ld.so.conf文件或者使用環境變量LD_LIBRARY_PATH來配置。
4.1 配置/etc/ld.so.conf文件
一般應用程序的庫文件不與系統庫文件放在同一目錄下,一般把應用程序的共享庫文件放在/usr/local/lib下,新建一個屬于自己的目錄apue,然后把剛才生成的libapue.so復制過去,同時在/etc/ld.so.conf中添加庫文件的路勁,一行一個,如:
/usr/local/lib/apue
/usr/X11R6/lib
/opt/lib
也可以在/etc/ld.so.conf.d目錄下創建一個.conf文件,將搜索路徑一行一個的加入該文件中。以后在編譯程序時,加上如下編譯選項,就可以使用共享庫了。
-L/usr/local/lib/apue –lapue
但需要注意的是:更改/etc/ld.so.conf的設置方法對于程序連接時的庫(包括共享庫和靜態庫)的定位已經足夠了,但是對于使用了共享庫的程序的執行還是不夠的。這是因為為了加快程序執行時對共享庫的定位速度,避免使用搜索路勁查找共享庫的低效率,動態加載器是直接讀取庫列表文件/etc/ld.so.cache來進行搜索的。Ld.so.cache是一個非文本的數據文件,不能直接編輯,它是根據/etc/ld.so.conf中設置的路勁由/sbin/ldconfig命令將這些搜索路徑下的共享庫文件集中在一起而生成的(ldconfig需要root權限才能執行)。
因此,為了保證程序執行時對庫的定位,在/etc/ld.so.conf中進行了庫文件搜索路徑設置后,還需要運行/sbin/ldconfig命令更新/etc/ld.so.cache文件。
4.2 修改環境變量LD_LIBRARY_PATH
方法一需要有root權限才能設置,而且當系統重新啟動后,所有的基于GTK2的程序在運行時都將使用新安裝的GTK+庫。不幸的是,由于GTK+版本的改變,這有時會給應用程序帶來兼容性的問題,造成程序運行不正常。為避免出現這種情況,可采用在環境變量LD_LIBRARY_PATH中指明庫的搜索路徑的方法,它不需要root權限,設置也簡單。
設置方法如下:
$ export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH
查看LD_LIBRARY_PATH的內容的方法為:
$ echo $LD_LIBRARY_PATH
注意:LD_LIBRARY_PATH的設定是全局的,過多的使用可能會影響到其他應用程序的運行,所以多用在調試中。
4.3 鏈接時通過-L選型顯示指定路徑
在程序鏈接時,對于庫文件(靜態庫和動態庫)的搜索路徑,可以通過-L參數顯示指定庫文件路徑。因為-L設置的路徑將被優先搜索,所以在鏈接的時候通常會以這種方式直接指定要鏈接的庫的路徑。如:
$ gcc main.c -o main -lfunc -L./
$ gcc test.c –o test –L./ -lapue
五、常見問題及解決方法
調用動態庫的時候,有時明明已經將庫的頭文件在目錄通過“-I”include進來了,庫所在文件通過“-L”參數引導,并指定了“-l”的庫名,但是通過ldd命令查看是,就是死活找不到指定鏈接的so庫文件,這時可通過修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
makefile里面怎么正確的編譯和連接生成的.so庫文件,然后又是在其他程序的makefile里面如何編譯和鏈接才能調用這個庫文件和函數?
- 通過export命令設置環境變量將庫的路徑添加到庫目錄中,這種方法不太方便:
export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH:$(pwd));
- LD_LIBRARY_PATH可以在/etc/profile,~/.profile或者./bash_profile里設置,或者.bashrc里,改完后運行source /etc/profile或./etc/profile;
- 將新的路徑添加進/etc/ld.so.conf,然后執行/sbin/ldconfig。