 簡析靜態庫與動態庫
							時間:2018-09-27      來源:未知(zhi)
							簡析靜態庫與動態庫
							時間:2018-09-27      來源:未知(zhi) 
							一、庫的簡介
當今程(cheng)(cheng)序(xu)(xu)(xu)員的(de)(de)(de)程(cheng)(cheng)序(xu)(xu)(xu)開發(fa)流程(cheng)(cheng)與50年前(qian)對比(bi)可謂是發(fa)生了(le)翻天覆地的(de)(de)(de)變化(hua):50年前(qian),那些(xie)“上古時(shi)期(qi)”的(de)(de)(de)大(da)神(shen)們(men)沒(mei)有(you)簡(jian)便(bian)(bian)的(de)(de)(de)可視化(hua)操作系統,沒(mei)有(you)詳(xiang) 盡的(de)(de)(de)API文檔(dang),沒(mei)有(you)方便(bian)(bian)的(de)(de)(de)面向(xiang)對象(xiang)語言(yan)(面向(xiang)過程(cheng)(cheng)語言(yan)剛(gang)剛(gang)興起),甚至連(lian)一(yi)些(xie)當今程(cheng)(cheng)序(xu)(xu)(xu)員認為某些(xie)“天生的(de)(de)(de)”功能(例如C語言(yan)的(de)(de)(de)printf函(han)數)都 沒(mei)有(you)。50年過去(qu)了(le),當今的(de)(de)(de)程(cheng)(cheng)序(xu)(xu)(xu)員們(men)可能無(wu)法(fa)體會過去(qu)的(de)(de)(de)大(da)神(shen)們(men)編程(cheng)(cheng)的(de)(de)(de)艱辛,因為有(you)一(yi)種工具(ju)包(bao)的(de)(de)(de)存(cun)在,使(shi)得編程(cheng)(cheng)大(da)大(da)簡(jian)化(hua),讓程(cheng)(cheng)序(xu)(xu)(xu)開發(fa)者更(geng)多地去(qu) 注重程(cheng)(cheng)序(xu)(xu)(xu)的(de)(de)(de)邏輯性而不是一(yi)些(xie)“細(xi)枝末(mo)節”。這種工具(ju)包(bao)就是庫。
庫(ku)(ku)(ku)(library)是(shi)一種可執(zhi)行代碼(ma)的(de)(de)(de)二進制形式,通常(chang)把一些(xie)常(chang)用(yong)的(de)(de)(de)函數制作成(cheng)各種函數庫(ku)(ku)(ku),然后(hou)被(bei)系(xi)統(tong)載入內(nei)(nei)存(cun)中運(yun)行。庫(ku)(ku)(ku)是(shi)許(xu)多前輩大(da)神們已 經(jing)寫好(hao)的(de)(de)(de)程序,程序開發者可以(yi)直接來(lai)調用(yong)這些(xie)功(gong)能(neng)程序來(lai)完成(cheng)相(xiang)應(ying)功(gong)能(neng),從而(er)簡化了程序的(de)(de)(de)開發工作。而(er)且如(ru)果不同的(de)(de)(de)應(ying)用(yong)程序調用(yong)同樣的(de)(de)(de)庫(ku)(ku)(ku),那 么內(nei)(nei)存(cun)內(nei)(nei)只需(xu)有一份該庫(ku)(ku)(ku)的(de)(de)(de)實(shi)例即可,節(jie)省了存(cun)儲空(kong)間。庫(ku)(ku)(ku)內(nei)(nei)一般都是(shi)各種標準程序、子(zi)程序、相(xiang)關文(wen)件以(yi)及目錄(lu)等(deng)的(de)(de)(de)集合,內(nei)(nei)置一些(xie)經(jing)常(chang)用(yong)的(de)(de)(de)程序 。主要有三種:
標準子程(cheng)序:例如三角函(han)數(shu)、反三角函(han)數(shu)等
標準程(cheng)序:例如(ru)解常(chang)微分(fen)方程(cheng)等(deng)
服(fu)務性程(cheng)序:例如(ru)輸(shu)入、輸(shu)出、磁(ci)盤操作、調試等。
以熟悉(xi)的C語(yu)言stdio庫(ku)為(wei)例(li)。stdio庫(ku)意為(wei)標(biao)準輸(shu)入輸(shu)出(chu)庫(ku)(standard input & output),該庫(ku)內集(ji)(ji)成的是用于控制輸(shu)入、輸(shu)出(chu)、輸(shu)出(chu)錯(cuo)誤的相關 功能(neng)函(han)數,例(li)如我們(men)熟悉(xi)的fopen()、fclose()、fread()、fwrite()、putchar()、getchar()、printf()、scanf()等函(han)數都集(ji)(ji)成在該庫(ku)內。從C89版(ban) 本開(kai)始,一般C語(yu)言編譯器(qi)都會自(zi)帶stdio庫(ku),只(zhi)需我們(men)在程(cheng)序中包含(han)頭(tou)文件stdio.h即(ji)可調用庫(ku)內的功能(neng)函(han)數。這樣就大大簡化了程(cheng)序的開(kai)發工作。
 
Linux系統(tong)下的庫(ku)分為靜(jing)態庫(ku)與動態庫(ku)兩種。二者(zhe)的不同點主要(yao)體現在載(zai)入(ru)時間的不同(見(jian)附圖1)。靜(jing)態庫(ku)在程(cheng)序(xu)編譯時的鏈(lian)接(jie)階段(duan)被鏈(lian)接(jie)到(dao)目標代 碼中,運行(xing)程(cheng)序(xu)時將不再需要(yao)靜(jing)態庫(ku)。編譯后的可執(zhi)(zhi)行(xing)程(cheng)序(xu)體積(ji)(ji)較(jiao)大。動態庫(ku)在程(cheng)序(xu)編譯時并不會馬上鏈(lian)接(jie)到(dao)目標代碼中,而是在執(zhi)(zhi)行(xing)階段(duan)才被程(cheng)序(xu) 載(zai)入(ru),因此編譯后的可執(zhi)(zhi)行(xing)程(cheng)序(xu)體積(ji)(ji)較(jiao)小,但是需要(yao)系統(tong)動態庫(ku)存(cun)在。
二、靜態庫簡介與制作
靜(jing)態庫(ku)(ku)(ku)在(zai)程(cheng)(cheng)序(xu)編(bian)(bian)譯(yi)的(de)(de)(de)“鏈接”階段生(sheng)效。在(zai)編(bian)(bian)譯(yi)過程(cheng)(cheng)中,若需要加(jia)(jia)載靜(jing)態庫(ku)(ku)(ku),則(ze)在(zai)鏈接階段,編(bian)(bian)譯(yi)器會拷貝一份完整(zheng)的(de)(de)(de)庫(ku)(ku)(ku)函(han)數代碼(ma),整(zheng)合到當前(qian)正在(zai) 編(bian)(bian)譯(yi)的(de)(de)(de)程(cheng)(cheng)序(xu)中,這樣(yang)在(zai)編(bian)(bian)譯(yi)完成(cheng)后(hou)庫(ku)(ku)(ku)就被(bei)整(zheng)合到了程(cheng)(cheng)序(xu)內部。這種加(jia)(jia)載庫(ku)(ku)(ku)的(de)(de)(de)方式稱為“靜(jing)態庫(ku)(ku)(ku)”。由于靜(jing)態庫(ku)(ku)(ku)與程(cheng)(cheng)序(xu)整(zheng)合在(zai)一起,因(yin)(yin)此(ci)程(cheng)(cheng)序(xu)體積較大 ,在(zai)程(cheng)(cheng)序(xu)運行(xing)時無需二次(ci)加(jia)(jia)載所需的(de)(de)(de)庫(ku)(ku)(ku),不過庫(ku)(ku)(ku)的(de)(de)(de)更(geng)新也變得困難。而(er)且(qie),由于靜(jing)態庫(ku)(ku)(ku)是采取“拷貝”的(de)(de)(de)方式來(lai)加(jia)(jia)載庫(ku)(ku)(ku),因(yin)(yin)此(ci)無法實現不同(tong)進程(cheng)(cheng)間的(de)(de)(de) 庫(ku)(ku)(ku)的(de)(de)(de)共享。
那么(me)如何制作一個(ge)靜態庫呢?
在Linux系統(tong)中(zhong),我們可(ke)以使用ar工具制作(zuo)一個(ge)(ge)靜態庫。ar是類似gcc的(de)一個(ge)(ge)GNU工具包(bao)內的(de)工具,作(zuo)用是建立、修改、提取歸(gui)檔(dang)文(wen)件。歸(gui)檔(dang)文(wen)件是包(bao)含(han)(han) 多個(ge)(ge)文(wen)件內容(rong)的(de)一個(ge)(ge)大文(wen)件,被包(bao)含(han)(han)文(wen)件的(de)原始內容(rong)、權(quan)限、時間戳、所有(you)者等屬性都保存(cun)于(yu)歸(gui)檔(dang)文(wen)件中(zhong),并且可(ke)以通(tong)過“提取”來還原該(gai)文(wen)件。
下(xia)面我(wo)將制作一個名為libmyhello.a的靜態庫。
(注(zhu)意:在(zai)Linux系(xi)統中(zhong),庫(ku)文(wen)(wen)件(jian)的文(wen)(wen)件(jian)名一般為libXXX.a或libXXX.so,其中(zhong)lib表示(shi)這是一個庫(ku),.a表示(shi)靜態(tai)(tai)庫(ku),.so表示(shi)動(dong)態(tai)(tai)庫(ku),XXX為庫(ku)名。在(zai) Windows系(xi)統中(zhong)以不同的文(wen)(wen)件(jian)后綴名區分(fen)靜態(tai)(tai)庫(ku)與動(dong)態(tai)(tai)庫(ku),其中(zhong).lib文(wen)(wen)件(jian)為靜態(tai)(tai)庫(ku),.dll文(wen)(wen)件(jian)為動(dong)態(tai)(tai)庫(ku)。)
第一步(bu):準備3個文件:hello.h、hello.c、test.c。其中hello.h和hello.c用(yong)于(yu)制作靜態庫,test.c是測試程序(xu)主(zhu)函(han)數。
 
第二步:將hello.c編譯(yi)生成目標文件hello.o
gcchello.c -c -o hello.o
第三步(bu):使用ar將hello.o制作成(cheng)靜態庫(ku)
arcrslibmyhello.ahello.o
 
第三步的(de)參(can)數(shu)解釋:
⒈c:表示無(wu)提示方式創建文件(jian)包
⒉r:在文(wen)件包中替代文(wen)件
⒊s:強制重新(xin)生(sheng)成文件(jian)包的符(fu)號(hao)表(biao)
此(ci)時就(jiu)生成了(le)文(wen)件(jian)名為(wei)libmyhello.a(庫(ku)名為(wei)myhello)的靜態庫(ku)。下(xia)一(yi)步就(jiu)可以將(jiang)該靜態庫(ku)鏈(lian)接到程(cheng)序(xu)中了(le)。
第四步(bu):編譯test.c,將剛制作的靜態(tai)庫加載至(zhi)程序(xu)內
gcctest.c -L. -lmyhello -o hello
其中參(can)數-L的意思(si)是添(tian)加(jia)(jia)所需增加(jia)(jia)的庫的路徑,-L.表(biao)示(shi)增加(jia)(jia)庫的路徑為當前路徑。參(can)數-l的意思(si)是在(zai)鏈接階段尋找(zhao)該庫并鏈接至程序中。
經過(guo)以上(shang)四步,我(wo)們就成功制作了一個靜態庫并將它成功地添(tian)加到(dao)程序(xu)中。執行程序(xu)hello即可看到(dao)結(jie)果。
  
并且若我們刪除(chu)庫(ku)(ku)(即libmyhello.a文件),再次執(zhi)行該程序仍然(ran)可(ke)以(yi)得到(dao)正確(que)的(de)結果。這(zhe)是因為靜態庫(ku)(ku)在鏈(lian)接階段已經和程序整合到(dao)一起(qi),即使原 始庫(ku)(ku)文件不(bu)存在,程序依然(ran)可(ke)以(yi)成功執(zhi)行。
三(san)、動態(tai)庫(共享(xiang)庫)的簡(jian)介與制作
靜態(tai)庫在使(shi)用過程中有許多的缺點,包括但不限于:庫與(yu)程序(xu)整合到一起,這(zhe)樣會(hui)使(shi)得(de)程序(xu)占(zhan)用空間(jian)變大;如果庫需要更新,則需要重新編譯;由(you)于 加載庫是采取拷貝(bei)的方式,這(zhe)樣程序(xu)與(yu)程序(xu)之間(jian)沒有實現庫的共(gong)享(xiang)……。
基于以上幾點,我們發明(ming)了(le)動態庫(ku)(ku)(ku)(ku)。與(yu)靜(jing)態庫(ku)(ku)(ku)(ku)不(bu)同的(de)(de)是,動態庫(ku)(ku)(ku)(ku)在(zai)鏈接階段并沒有真正的(de)(de)整合到程(cheng)(cheng)序(xu)(xu)內部(bu),而(er)是保留(liu)了(le)庫(ku)(ku)(ku)(ku)的(de)(de)一個“線索”,當(dang)我們執 行該程(cheng)(cheng)序(xu)(xu)時,程(cheng)(cheng)序(xu)(xu)會按照這條(tiao)“線索”與(yu)當(dang)前系統的(de)(de)環境(jing)變量尋找庫(ku)(ku)(ku)(ku)的(de)(de)真正所在(zai)位置(zhi)并加載。這樣(yang)做(zuo)的(de)(de)好(hao)處是將(jiang)庫(ku)(ku)(ku)(ku)與(yu)程(cheng)(cheng)序(xu)(xu)人為(wei)分離(li),這樣(yang)便于庫(ku)(ku)(ku)(ku)的(de)(de)更 新與(yu)維護,同時多個程(cheng)(cheng)序(xu)(xu)間(jian)只需保留(liu)一份庫(ku)(ku)(ku)(ku)的(de)(de)實例即(ji)可(ke),無需拷貝庫(ku)(ku)(ku)(ku)而(er)浪費內存。不(bu)過這樣(yang)做(zuo)的(de)(de)缺點就(jiu)是程(cheng)(cheng)序(xu)(xu)對(dui)動態庫(ku)(ku)(ku)(ku)有依賴(lai)性,即(ji)程(cheng)(cheng)序(xu)(xu)無法脫離(li)庫(ku)(ku)(ku)(ku) 而(er)獨立運行。
(如果有玩(wan)過(尤(you)其是盜版(ban))游戲(xi)的(de)同學,一定遇到(dao)過“缺少(shao)XXX.dll文(wen)件(jian)(jian)”的(de)問題(ti)從(cong)而導致游戲(xi)無法(fa)正(zheng)確運行。.dll文(wen)件(jian)(jian)即Windows系統(tong)下的(de)動態庫 文(wen)件(jian)(jian),缺少(shao)該文(wen)件(jian)(jian)即使(shi)游戲(xi)能夠(gou)正(zheng)確地安裝到(dao)電腦上,也會因缺少(shao)相應庫文(wen)件(jian)(jian)而無法(fa)執行。)
那(nei)么如何制作一個動態(tai)庫呢?
我們(men)可以使(shi)用gcc直接制作一(yi)個(ge)自己的動態庫。
第一(yi)步:需要準備(bei)3個文件:hello.h、hello.c、test.c。其(qi)中hello.h和hello.c用(yong)于制(zhi)作動態(tai)庫,test.c是測(ce)試(shi)程序主函數。(代(dai)碼與上(shang)面相同,略 )
第二步(bu):使用gcc編譯生成(cheng)動態庫。
gcchello.c -fPIC -c -o hello.o
gcchello.o -shared -o libmyhello.so
(或者(zhe)直接寫(xie)成一(yi)步:gcchello.c -fPIC -shared -o libmyhello.so)
 
第(di)二步的(de)參(can)數解釋:
⒈ -fPIC(或-fpic):表示編譯為位(wei)置(zhi)獨(du)立(li)的(de)代(dai)(dai)(dai)碼(ma)。位(wei)置(zhi)獨(du)立(li)的(de)代(dai)(dai)(dai)碼(ma)即位(wei)置(zhi)無關代(dai)(dai)(dai)碼(ma),在(zai)可(ke)執(zhi)行程(cheng)序加載(zai)的(de)時(shi)候可(ke)以存放在(zai)內存內的(de)任何(he)位(wei)置(zhi) 。若不使用該選項,則編譯后(hou)的(de)代(dai)(dai)(dai)碼(ma)是(shi)位(wei)置(zhi)相關的(de)代(dai)(dai)(dai)碼(ma),在(zai)可(ke)執(zhi)行程(cheng)序加載(zai)時(shi)仍然是(shi)通過代(dai)(dai)(dai)碼(ma)拷貝的(de)方式來滿足(zu)不同(tong)的(de)進程(cheng)的(de)需要,并沒有(you)實現真(zhen)正 意義上(shang)的(de)位(wei)置(zhi)共享。
⒉ -shared:指定(ding)生成動(dong)態鏈(lian)接庫。
此時就生(sheng)成了文件名為(wei)libmyhello.so(庫(ku)名為(wei)myhello)的動態(tai)庫(ku)。下一步就可以將該動態(tai)庫(ku)鏈接(jie)到程(cheng)序中了。
第三步:編(bian)譯test.c,將剛制作的動態(tai)庫加(jia)載至程序(xu)內。
gcctest.c -L. -lmyhello -o hello
此時就生(sheng)成了可執(zhi)行(xing)程序(xu)hello。不過,當(dang)我們執(zhi)行(xing)該程序(xu)的時候(hou),會發生(sheng)錯誤:
 
錯誤信息為(wei)“當加載共享(xiang)庫的(de)時候,無法找(zhao)到(dao)libmyhello.so的(de)位置:沒有該文件或目錄”。
上文(wen)說過,動(dong)態(tai)庫(ku)(ku)在鏈接(jie)階段并沒有真正地(di)整合到程序中,而是保留了(le)一(yi)個指(zhi)向該庫(ku)(ku)的(de)(de)“線索”。當程序在加(jia)載該動(dong)態(tai)庫(ku)(ku)的(de)(de)時(shi)候(hou),需要(yao)依照線索找到 動(dong)態(tai)庫(ku)(ku)所(suo)在的(de)(de)位置。對(dui)于(yu)Linux系統而言,在可執行程序加(jia)載動(dong)態(tai)庫(ku)(ku)的(de)(de)時(shi)候(hou),不(bu)僅要(yao)知(zhi)道該庫(ku)(ku)的(de)(de)名(ming)字,還(huan)需要(yao)知(zhi)道其絕對(dui)路徑。因(yin)此(ci),我們需要(yao)再聲(sheng)明 動(dong)態(tai)庫(ku)(ku)的(de)(de)絕對(dui)位置,這樣才能正確地(di)加(jia)載動(dong)態(tai)庫(ku)(ku)。
我們可以使用ldd指(zhi)令查看程序(xu)加載(zai)(zai)庫的(de)情況。在(zai)執行hello程序(xu)的(de)時候,系統無(wu)法找到libmyhello.so的(de)絕(jue)對路徑(jing),因此(ci)無(wu)法加載(zai)(zai)庫。
  
第四步:定位自己制作的動態庫。
  
要(yao)想讓自己制作的(de)動(dong)態庫(ku)生效,我們(men)需要(yao)了解正常情況下系(xi)統是(shi)如何加載(zai)一個(ge)動(dong)態庫(ku)的(de)。以我們(men)熟悉的(de)stdio庫(ku)為(wei)例,系(xi)統在加載(zai)標準輸入輸出庫(ku)時有 以下幾(ji)個(ge)步驟:(見附圖2)
⒈執(zhi)行(xing)./hello指令,終端(duan)解釋該指令,終端(duan)指示(shi)應加載動態(tai)庫stdio,尋找存放(fang)動態(tai)庫的配置文件(jian)。
⒉存放動(dong)態庫的(de)配置(zhi)文(wen)件默認目錄為/etc/ld.so.conf.d/以(yi)及下屬的(de)眾多子目錄內的(de)配置(zhi)文(wen)件。配置(zhi)文(wen)件指示該庫的(de)絕(jue)對路徑在/usr/lib或/lib下。
⒊去往/usr/lib或(huo)/lib,將存儲的stdio庫加載到程序hello中。
因(yin)此(ci)我們有三種方法(fa)讓自己制作的動態庫生效:
⒈把(ba)自己制作的(de)庫拷貝到/usr/lib和(he)/lib下。
⒉在LD_LIBRARY_PATH環境變(bian)量(liang)中添加自(zi)己制作(zuo)的庫所在的位置。
⒊添加(jia)/etc/ld.so.conf.d/XXX.conf文件(jian)(XXX需要自己命名),把庫(ku)所在的路徑(jing)添加(jia)到文件(jian)末尾(wei)并執(zhi)行ldconfig刷新。
  
(注(zhu)意以下(xia)三種(zhong)方法的(de)異同,以及每種(zhong)方法執行時ldd指令所顯示的(de)區別。)
第(di)一種:將庫拷貝到/usr/lib和/lib下。
sudocp libmyhello.so /usr/lib
sudocp libmyhello.so /lib
此(ci)時再執行./hello即可得到正確的(de)顯示結果。
 
第二種:修(xiu)改LD_LIBRARY_PATH環境變量
sudo vim /etc/bash.bashrc
在文件后,添加:
export
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linux/dongtaiku
保存(cun)退出(chu),重啟終端,此時再執(zhi)行./hello即(ji)可(ke)得到正確的顯示結果。
 
第三(san)種:添加/etc/ld.so.conf.d/XXX.conf文件
sudo vim /etc/ld.so.conf.d/my.conf
在文(wen)件(jian)內添加動態庫(ku)的目錄:
/home/linux/dongtaiku
保存(cun)退出(chu),執行ldconfig使設置生效。
sudoldconfig
此時再執行./hello即可得到正(zheng)確的顯示結果。
 
以上(shang)就是讓動態庫生效(xiao)的三種方法。
四、結語
庫的(de)(de)(de)(de)(de)(de)存在(zai),使得我們的(de)(de)(de)(de)(de)(de)編程(cheng)(cheng)過程(cheng)(cheng)大(da)(da)大(da)(da)簡(jian)化,程(cheng)(cheng)序(xu)員(yuan)可以將更多的(de)(de)(de)(de)(de)(de)精力放在(zai)程(cheng)(cheng)序(xu)的(de)(de)(de)(de)(de)(de)邏輯(ji)上。并且(qie),庫的(de)(de)(de)(de)(de)(de)產生直接改(gai)變了人(ren)們對于編程(cheng)(cheng)語言(yan)的(de)(de)(de)(de)(de)(de)認識,可以 說間接促進了當代許多面向對象(xiang)語言(yan)的(de)(de)(de)(de)(de)(de)誕生。當代的(de)(de)(de)(de)(de)(de)許多語言(yan),例如c++、java、c#、javascript包(bao)括近(jin)大(da)(da)火的(de)(de)(de)(de)(de)(de)Node.js,都是(shi)集合了大(da)(da)量的(de)(de)(de)(de)(de)(de)工具庫 ,庫內包(bao)含了許多種編程(cheng)(cheng)時可能調用的(de)(de)(de)(de)(de)(de)功(gong)能。這(zhe)樣大(da)(da)大(da)(da)地方便了程(cheng)(cheng)序(xu)開發人(ren)員(yuan)。
后留一道(dao)思考題給各(ge)位:當(dang)靜(jing)態(tai)庫(ku)(ku)與動態(tai)庫(ku)(ku)的庫(ku)(ku)名重名時,系統在(zai)加(jia)載庫(ku)(ku)時是以哪(na)個庫(ku)(ku)為準(zhun)呢?例如有靜(jing)態(tai)庫(ku)(ku)libmyhello.a和動態(tai)庫(ku)(ku)libmyhello.so ,在(zai)編(bian)譯時,都(dou)執行(xing):
gcctest.c –L. –lmyhello –o hello
這時(shi)系統以哪(na)個(ge)庫為準(zhun)呢(ni)?

