二者的不同點在于代碼被載入的時刻不同胁赢。
靜態(tài)庫的代碼在編譯過程中已經(jīng)被載入可執(zhí)行程序,因此體積比較大。
動態(tài)庫(共享庫)的代碼在可執(zhí)行程序運行時才載入內(nèi)存系馆,在編譯過程中僅簡單的引用代兵,因此代碼體積比較小。
不同的應用程序如果調用相同的庫,那么在內(nèi)存中只需要有一份該動態(tài)庫(共享庫)的實例土辩。
靜態(tài)庫和動態(tài)庫的最大區(qū)別,靜態(tài)情況下,把庫直接加載到程序中,而動態(tài)庫鏈接的時候,它只是保留接口,將動態(tài)庫與程序代碼獨立,這樣就可以提高代碼的可復用度,和降低程序的耦合度。
靜態(tài)庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態(tài)庫松忍。
動態(tài)庫在程序編譯時并不會被連接到目標代碼中摊溶,而是在程序運行是才被載入,因此在程序運行時還需要動態(tài)庫存在
一 靜態(tài)庫
這類庫的名字一般是libxxx.a;利用靜態(tài)函數(shù)庫編譯成的文件比較大,因為整個 函數(shù)庫的所有數(shù)據(jù)都會被整合進目標代碼中,他的優(yōu)點就顯而易見了,即編譯后的執(zhí)行程序不需要外部的函數(shù)庫支持硼婿,因為所有使用的函數(shù)都已經(jīng)被編譯進去了。當然這也會成為他的缺點记焊,因為如果靜態(tài)函數(shù)庫改變了瓢颅,那么你的程序必須重新編譯信柿。
靜態(tài)庫的代碼在編譯時鏈接到應用程序中,因此編譯時庫文件必須存在,并且需要通過“-L”參數(shù)傳遞路徑給編譯器,應用程序在開始執(zhí)行時,庫函數(shù)代碼將隨程序一起調入進程內(nèi)存段直到進程結束蝗岖,其執(zhí)行過程不需要原靜態(tài)庫存在铅鲤。
在UNIX中,使用ar命令創(chuàng)建或者操作靜態(tài)庫
ar archivefile objfile
archivefile:archivefile是靜態(tài)庫的名稱
objfile:objfile是已.o為擴展名的中間目標文件名骇塘,可以多個并列
參數(shù) 意義
-r 將objfile文件插入靜態(tài)庫尾或者替換靜態(tài)庫中同名文件
-x 從靜態(tài)庫文件中抽取文件objfile
-t 打印靜態(tài)庫的成員文件列表
-d 從靜態(tài)庫中刪除文件objfile
-s 重置靜態(tài)庫文件索引
-v 創(chuàng)建文件冗余信息
-c 創(chuàng)建靜態(tài)庫文件
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;??
}??
需要C/C++ Linux服務器架構師學習資料加群563998835(資料包括C/C++,Linux溢陪,golang技術咆霜,Nginx,ZeroMQ,MySQL戳寸,Redis袖瞻,fastdfs奕枢,MongoDB谷浅,ZK掌猛,流媒體,CDN,P2P十偶,K8S,Docker坦敌,TCP/IP,協(xié)程穷当,DPDK德撬,ffmpeg等)摇天,免費分享
1.編譯成靜態(tài)庫
無論靜態(tài)庫纯丸,還是動態(tài)庫,都是由.o文件創(chuàng)建的若未。因此倾鲫,我們必須將源程序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() 里面還使用了其他的庫函數(shù)比如pthread_create,則最后生成Out1 時還需 -lpthread,但ar 時可以不用邻吞,只需要在 include 的頭文件中找到函數(shù)符號聲明即可瘦癌,但最終生成可執(zhí)行文件時需要找到所有的符號定義牙寞。
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)
二: 動態(tài)庫
這類庫的名字一般是libxxx.so;相對于靜態(tài)函數(shù)庫,動態(tài)函數(shù)庫在編譯的時候 并沒有被編譯進目標代碼中莫秆,你的程序執(zhí)行到相關函數(shù)時才調用該函數(shù)庫里的相應函數(shù)间雀,因此動態(tài)函數(shù)庫所產(chǎn)生的可執(zhí)行文件比較小。由于函數(shù)庫沒有被整合進你的程序镊屎,而是程序運行時動態(tài)的申請并調用惹挟,所以程序的運行環(huán)境中必須提供相應的庫。動態(tài)函數(shù)庫的改變并不影響你的程序缝驳,所以動態(tài)函數(shù)庫的升級比較方便
不同的UNIX系統(tǒng),鏈接動態(tài)庫方法连锯,實現(xiàn)細節(jié)不一樣
編譯PIC型.o中間文件的方法一般是采用C語言編譯器的-KPIC或者-fpic選項,有的UNIX版本C語言編譯器默認帶上了PIC標準.創(chuàng)建最終動態(tài)庫的方法一般采用C語言編譯器的-G或者-shared選項,或者直接使用工具ld創(chuàng)建用狱。
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態(tài)連接庫(讓連接器生成T類型的導出符號表运怖,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接齿拂。相當于一個可執(zhí)行文件-fPIC:表示編譯為位置獨立的代碼驳规,不用此選項的話編譯后的代碼是位置相關的所以動態(tài)載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的署海。(轉者注:共享庫各段的加載地址并沒有定死吗购,可以加載到任意位置,因為指令中沒有使用絕對地址(相對于鏈接后的可執(zhí)行文件各segment來說)砸狞,因此稱為位置無關代碼)-L.:表示要連接的庫在當前目錄中-ltest:編譯器查找動態(tài)連接庫時有隱含的命名規(guī)則捻勉,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
這里分別將源文件d1.c和d2.c編譯為動態(tài)庫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 /*根據(jù)中間目標文件d1.o創(chuàng)建動態(tài)庫文件d1.so*/
g++ -shared -o libd2.so d2.o /*根據(jù)中間目標文件d2.o創(chuàng)建動態(tài)庫文件d2.so*/
或者直接一步到位
g++ -O -fpic -shared -o libd1.so d1.cpp
g++ -O -fpic -shared -o libd2.so d2.cpp
某些版本的gcc上也可以使用-G替換-shared選項
調用動態(tài)庫
隱式調用動態(tài)庫
/**************??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踱启,在運行過程中做動態(tài)鏈接,從而得知這個可執(zhí)行文件依賴于哪些共享庫研底,每個共享庫都在什么路徑下埠偿,加載到進程地址空間的什么地址。
總之榜晦,共享庫的搜索路徑由動態(tài)鏈接器決定冠蒋,從ld.so(8)的Man Page可以查到共享庫路徑的搜索順序:
1. 首先在環(huán)境變量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. 如果上述步驟都找不到,則到默認的系統(tǒng)路徑中查找缩宜,先是/usr/lib然后是/lib肘迎。
不同的UNIX所依賴的動態(tài)庫查找路徑環(huán)境變量名稱各不相同
UNIX版本 動態(tài)庫查找路徑環(huán)境變量
AIX LIB_PATH
LINUX LD_LIBRARY_PATH
HP_UNIX PAHT
SCO UNIX LD_LIBRARY_PATH
動態(tài)鏈接庫取代靜態(tài)庫的好處之一就是可以隨時升級庫的內(nèi)容。
當動態(tài)庫被接口完全相同的庫文件取代后,可執(zhí)行程序能迅速的切換到新動態(tài)庫中代碼锻煌,省去了編譯的麻煩膜宋。
顯式調用動態(tài)庫
顯式調用動態(tài)庫,編譯時無需庫文件,執(zhí)行時動態(tài)可存儲于任意位置,庫里共享對象必須先申請后使用,不同動態(tài)庫版本,只要其共享對象接口相同,就可以直接動態(tài)加載。注意添加 "-ldl" 編譯參數(shù)炼幔。
//打開動態(tài)庫??
#include<dlfcn.h>??
void?*dlopen(const?char?*?pathname,int?mode);??
//獲取動態(tài)庫對象地址??
include<dlfcn.h>??
void?*dlsym(void?*handle,const?char?*name);??
//錯誤檢測??
include<dlfcn.h>??
char?*dlerror(vid);??
//關閉動態(tài)庫??
include<dlfcn.h>??
int?dlclose(void?*?handle);??
動態(tài)庫的加載或多或少會占用一定的系統(tǒng)資源,比如內(nèi)存等。因此當不需要或者一段時間內(nèi)不需要共享動態(tài)庫時就要卸載之史简。函數(shù)dlclose關閉參數(shù)handle所指向的動態(tài)庫乃秀,卸載其所占的內(nèi)存等資源,此調用后參數(shù)handle無效。
實際上圆兵,由于動態(tài)庫可能同時被多個進程共享,當一個進程指向dlclose時跺讯,資源并不馬上被卸載,只有當全部進程都宣布關閉動態(tài)庫后,操作系統(tǒng)才開始回收動態(tài)庫資源。
總結:
編譯靜態(tài)庫時先使用-c選項,再利用ar工具產(chǎn)生.編譯動態(tài)庫的方式依不同版本的UNXI而定殉农。隱式調用動態(tài)庫與靜態(tài)庫的用法相一致,而顯示調用動態(tài)庫則需要借助動態(tài)加載共享庫函數(shù)族刀脏。
隱式調用動態(tài)庫和靜態(tài)庫使用方法一致,使用靜態(tài)庫和使用動態(tài)庫編譯成目標程序使用的gcc命令完全一樣,那當靜態(tài)庫和動態(tài)庫同名時超凳,gcc命令會使用哪個庫文件呢愈污?
通過測試可以發(fā)現(xiàn),當靜態(tài)庫和動態(tài)庫同名時, gcc命令將優(yōu)先使用動態(tài)庫.為了確保使用的是靜態(tài)庫, 編譯時可以加上 -static 選項轮傍,因此多第三方程序為了確保在沒有相應動態(tài)庫時運行正常暂雹,喜歡在編譯最后應用程序時加入-static