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 #include"hello.h" using namespace std; void hello(void) { cout <
/****************** main.cpp **************/ #include"hello.h" int main(int argc,char *argv[]) { hello(); return 0; }
需要C/C++ Linux伺服器架構師學習資料私信“資料”(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享
1.編譯成靜态庫
無論靜态庫,還是動态庫,都是由.o檔案建立的。是以,我們必須将源程式hello.c通過gcc先編譯成.o檔案。
[email protected]:~/weiming/tt> g++ -o hello.o -c hello.cpp
[email protected]:~/weiming/tt> ar cqs libHello.a hello.o
[email protected]:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp
2.連結
[email protected]:~/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 的頭檔案中找到函數符号聲明即可,但最終生成可執行檔案時需要找到所有的符号定義。
[email protected]:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp Out1
[email protected]:~/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 #include "d1.h" using namespace std int p = 1; void print() { cout<
/************ d2.h***************/ void print();
/*************** d2.cpp *******************/ #include #include "d2.h" using namespace std; int p = 2; void print() { cout<
LINUX和其他gcc編譯器
g++ -fpic -c d1.cpp d2.cpp
g++ -shared -o libd1.so d1.o
g++ -shared -o libd2.so d2.o
或者直接一步到位
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)
[email protected]:~/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 void *dlopen(const char * pathname,int mode); //擷取動态庫對象位址 include void *dlsym(void *handle,const char *name); //錯誤檢測 include char *dlerror(vid); //關閉動态庫 include int dlclose(void * handle);
動态庫的加載或多或少會占用一定的系統資源,比如記憶體等。是以當不需要或者一段時間内不需要共享動态庫時就要解除安裝之。函數dlclose關閉參數handle所指向的動态庫,解除安裝其所占的記憶體等資源,此調用後參數handle無效。
實際上,由于動态庫可能同時被多個程序共享,當一個程序指向dlclose時,資源并不馬上被解除安裝,隻有當全部程序都宣布關閉動态庫後,作業系統才開始回收動态庫資源。
總結:
編譯靜态庫時先使用-c選項,再利用ar工具産生.編譯動态庫的方式依不同版本的UNXI而定。隐式調用動态庫與靜态庫的用法相一緻,而顯示調用動态庫則需要借助動态加載共享庫函數族。
隐式調用動态庫和靜态庫使用方法一緻,使用靜态庫和使用動态庫編譯成目标程式使用的gcc指令完全一樣,那當靜态庫和動态庫同名時,gcc指令會使用哪個庫檔案呢?
通過測試可以發現,當靜态庫和動态庫同名時, gcc指令将優先使用動态庫.為了確定使用的是靜态庫, 編譯時可以加上 -static 選項,是以多第三方程式為了確定在沒有相應動态庫時運作正常,喜歡在編譯最後應用程式時加入-static