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)
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:表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正程式碼片段共用的目的。
-L.:表示要串連的庫在目前的目錄中
-ltest:編譯器尋找動態串連庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱
LD_LIBRARY_PATH:這個環境變數指示動態連接器可以裝載動態庫的路徑。
當然如果有root許可權的話,可以修改/etc/ld.so.conf檔案,然後調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root許可權,那麼只能採用輸出LD_LIBRARY_PATH的方法了。
這裡分別將源檔案d1.c和d2.c編譯為動態庫d1.so和d2.so.
/************ d1.h***************/void print();
/*************** d1.cpp *******************/#include <iostream>#include "d1.h"using namespace stdint 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)
在上例中,動態庫libd.so與執行程式在同一目錄下,如果將libd.so移走再執行程式,程式將不能正常執行。
當需要載入動態庫代碼時,UNIX會按照某種路徑尋找動態庫
通知UNIX系統動態庫的正確位置有如下兩種方法.,
1)帶編譯路徑
#g++ -o dOut main.cpp ./libd.so (或者g++ -o dOut main.cpp -L./ -ld)
當執行程式時,程式會自動在當前路徑下操作動態庫libd.so
2)更改環境變數
#LD_LIBPARY_PATH=./
#export LD_LIBPARY_PATH
不同的UNIX所依賴的動態庫尋找路徑環境變數名稱各不相同
UNIX版本 動態庫尋找路徑環境變數
AIX LIB_PATH
LINUX LD_LIBPARY_PATH
HP_UNIX PAHT
SCO UNIX LD_LIBPARY_PAHT
動態連結程式庫取代靜態庫的好處之一就是可以隨時升級庫的內容。
當動態庫被介面完全相同的庫檔案取代後,可執行程式能迅速的切換到新動態庫中代碼,省去了編譯的麻煩。
例如將libd2.so換成libd.so
顯示調用動態庫
顯示調用動態庫,編譯時間無需庫檔案,執行時動態可儲存於任意位置,庫裡共用對象必須先申請後使用,不同動態庫版本,只要其共用對象介面相同,就可以直接動態載入。
//開啟動態庫#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