linux下有兩種庫:動態庫和靜態庫(共享庫)
二者的不同點在于代碼被載入的時刻不同。
靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積比較大。
動態庫(共享庫)的代碼在可執行程序運行時才載入內存,在編譯過程中僅簡單的引用,因此代碼體積比較小。
不同的應用程序如果調用相同的庫,那么在內存中只需要有一份該動態庫(共享庫)的實例。
靜態庫和動態庫的最大區別,靜態情況下,把庫直接加載到程序中,而動態庫鏈接的時候,它只是保留接口,將動態庫與程序代碼獨立,這樣就可以提高代碼的可復用度,和降低程序的耦合度。
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。
動態庫在程序編譯時并不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在
一 靜態庫
這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因為整個 函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
靜態庫的代碼在編譯時鏈接到應用程序中,因此編譯時庫文件必須存在,并且需要通過“-L”參數傳遞路徑給編譯器,應用程序在開始執行時,庫函數代碼將隨程序一起調入進程內存段直到進程結束,其執行過程不需要原靜態庫存在。
在UNIX中,使用ar命令創建或者操作靜態庫
ar archivefile objfile
archivefile:archivefile是靜態庫的名稱
objfile:objfile是已.o為擴展名的中間目標文件名,可以多個并列
參數 意義
-r 將objfile文件插入靜態庫尾或者替換靜態庫中同名文件
-x 從靜態庫文件中抽取文件objfile
-t 打印靜態庫的成員文件列表
-d 從靜態庫中刪除文件objfile
-s 重置靜態庫文件索引
-v 創建文件冗余信息
-c 創建靜態庫文件
example:
/****************** hello.h **************/
void hello(void);
/****************** hello.cpp **************/
#include<IOStream>
#include"hello.h" using namespace std; void hello(void) { cout <<"Hello "<<endl;
}
/****************** main.cpp **************/
#include"hello.h"
int main(int argc,char *argv[])
{ hello(); return 0; }
1.編譯成靜態庫
無論靜態庫,還是動態庫,都是由.o文件創建的。因此,我們必須將源程序hello.c通過gcc先編譯成.o文件。
hc@linux-v07j:~/weiming/tt> g++ -o hello.o -c hello.cpp
hc@linux-v07j:~/weiming/tt> ar cqs libHello.a hello.o
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp
2.鏈接
hc@linux-v07j:~/weiming/tt> g++ main.cpp libHello.a -o Out1 (g++ -o aOut main.cpp ./libHello.a 或者 g++ -o aOut main.cpp -L./ -lHello)
注意:如果hello() 里面還使用了其他的庫函數比如pthread_create,則最后生成Out1 時還需 -lpthread,但ar 時可以不用,只需要在 include 的頭文件中找到函數符號聲明即可,但最終生成可執行文件時需要找到所有的符號定義。
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp Out1
hc@linux-v07j:~/weiming/tt> ldd Out1
linux-gate.so.1 => (0xffffe000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e36000)
libm.so.6 => /lib/libm.so.6 (0xb7e11000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7e06000)
libc.so.6 => /lib/libc.so.6 (0xb7ce3000)
/lib/ld-linux.so.2 (0xb7f1b000)
二: 動態庫
這類庫的名字一般是libxxx.so;相對于靜態函數庫,動態函數庫在編譯的時候 并沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由于函數庫沒有被整合進你的程序,而是程序運行時動態的申請并調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變并不影響你的程序,所以動態函數庫的升級比較方便
不同的UNIX系統,鏈接動態庫方法,實現細節不一樣
編譯PIC型.o中間文件的方法一般是采用C語言編譯器的-KPIC或者-fpic選項,有的UNIX版本C語言編譯器默認帶上了PIC標準.創建最終動態庫的方法一般采用C語言編譯器的-G或者-shared選項,或者直接使用工具ld創建。
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件
-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。(轉者注:共享庫各段的加載地址并沒有定死,可以加載到任意位置,因為指令中沒有使用絕對地址(相對于鏈接后的可執行文件各segment來說),因此稱為位置無關代碼)
-L.:表示要連接的庫在當前目錄中
-ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
這里分別將源文件d1.c和d2.c編譯為動態庫d1.so和d2.so.
/************ d1.h***************/
void print();
/*************** d1.cpp *******************/
#include <iostream>
#include "d1.h" using namespace std int p = 1; void print() { cout<< p <<endl;
}
/************ d2.h***************/
void print();
/*************** d2.cpp *******************/
#include <iostream>
#include "d2.h" using namespace std; int p = 2; void print() { cout<< p <<endl;
}
LINUX和其他gcc編譯器
g++ -fpic -c d1.cpp d2.cpp /* 編譯為.o為擴展名的中間目標文件d1.o,d2.o*/
g++ -shared -o libd1.so d1.o /*根據中間目標文件d1.o創建動態庫文件d1.so*/
g++ -shared -o libd2.so d2.o /*根據中間目標文件d2.o創建動態庫文件d2.so*/
或者直接一步到位
g++ -O -fpic -shared -o libd1.so d1.cpp
g++ -O -fpic -shared -o libd2.so d2.cpp
某些版本的gcc上也可以使用-G替換-shared選項
調用動態庫
隱式調用動態庫
/************** main.cpp *********************/
void print(); //或者用#include"d1.h"(#include"d2.h")替換
int main(int argc,char *argv[])
{ print(); }
#cp ./libd1.so libd.so (cp ./libd2.so libd.so )
# g++ -o dOut main.cpp ./libd.so (或者g++ -o dOut main.cpp -L./ -ld)
hc@linux-v07j:~/weiming/tt/dd> ldd dOut
linux-gate.so.1 => (0xffffe000)
libd.so => ./libd.so (0xb7f0f000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e2b000)
libm.so.6 => /lib/libm.so.6 (0xb7e06000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7dfa000)
libc.so.6 => /lib/libc.so.6 (0xb7cd8000)
/lib/ld-linux.so.2 (0xb7f12000)
ldd模擬運行一遍main,在運行過程中做動態鏈接,從而得知這個可執行文件依賴于哪些共享庫,每個共享庫都在什么路徑下,加載到進程地址空間的什么地址。
總之,共享庫的搜索路徑由動態鏈接器決定,從ld.so(8)的Man Page可以查到共享庫路徑的搜索順序:
1. 首先在環境變量LD_LIBRARY_PATH所記錄的路徑中查找。
2. 在程序鏈接時指定的 rpath 中查找,可以 readelf binfile | grep RPATH 。
3. 然后從緩存文件/etc/ld.so.cache中查找。這個緩存文件由/sbin/ldconfig命令讀取配置文件/etc/ld.so.conf 之后生成。
(也可以在 ld.so.conf.d 目錄下增加 *.conf 文件,里面寫入庫路徑,在 ld.so.conf 中 include ld.so.conf.d/*.conf )
4. 如果上述步驟都找不到,則到默認的系統路徑中查找,先是/usr/lib然后是/lib。
不同的UNIX所依賴的動態庫查找路徑環境變量名稱各不相同
UNIX版本 動態庫查找路徑環境變量
AIX LIB_PATH
LINUX LD_LIBRARY_PATH
HP_UNIX PAHT
SCO UNIX LD_LIBRARY_PATH
動態鏈接庫取代靜態庫的好處之一就是可以隨時升級庫的內容。
當動態庫被接口完全相同的庫文件取代后,可執行程序能迅速的切換到新動態庫中代碼,省去了編譯的麻煩。
顯式調用動態庫
顯式調用動態庫,編譯時無需庫文件,執行時動態可存儲于任意位置,庫里共享對象必須先申請后使用,不同動態庫版本,只要其共享對象接口相同,就可以直接動態加載。注意添加 "-ldl" 編譯參數。
//打開動態庫
#include<dlfcn.h>
void *dlopen(const char * pathname,int mode); //獲取動態庫對象地址 include<dlfcn.h>
void *dlsym(void *handle,const char *name); //錯誤檢測 include<dlfcn.h>
char *dlerror(vid); //關閉動態庫 include<dlfcn.h>
int dlclose(void * handle);
動態庫的加載或多或少會占用一定的系統資源,比如內存等。因此當不需要或者一段時間內不需要共享動態庫時就要卸載之。函數dlclose關閉參數handle所指向的動態庫,卸載其所占的內存等資源,此調用后參數handle無效。
實際上,由于動態庫可能同時被多個進程共享,當一個進程指向dlclose時,資源并不馬上被卸載,只有當全部進程都宣布關閉動態庫后,操作系統才開始回收動態庫資源。
總結:
編譯靜態庫時先使用-c選項,再利用ar工具產生.編譯動態庫的方式依不同版本的UNXI而定。隱式調用動態庫與靜態庫的用法相一致,而顯示調用動態庫則需要借助動態加載共享庫函數族。
隱式調用動態庫和靜態庫使用方法一致,使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令完全一樣,那當靜態庫和動態庫同名時,gcc命令會使用哪個庫文件呢?
通過測試可以發現,當靜態庫和動態庫同名時, gcc命令將優先使用動態庫.為了確保使用的是靜態庫, 編譯時可以加上 -static 選項,因此多第三方程序為了確保在沒有相應動態庫時運行正常,喜歡在編譯最后應用程序時加入-static