關於動態連接庫與靜態連接庫

簡介php

1. 靜態函數庫html

    這類庫的名字通常是libxxx.a;利用靜態函數庫編譯成的文件比較大,由於整個 函數庫的全部數據都會被整合進目標代碼中,他的優勢就顯而易見了,即編譯後的執行程序不須要外部的函數庫支持,由於全部使用的函數都已經被編譯進去了。固然這也會成爲他的缺點,由於若是靜態函數庫改變了,那麼你的程序必須從新編譯linux

2. 動態函數庫數據庫

    這類庫的名字通常是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候 並無被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫裏的相應函數,所以動態函數庫所產生的可執行文件比較小。因爲函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,因此程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,因此動態函數庫的升級比較方便。 
linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib編程

即:緩存

靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將再也不須要該靜態庫app

動態庫在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行是才被載入,所以在程序運行時還須要動態庫存在編程語言

可是通過幾回項目後發現:動態連接庫的好處,特別是較大的軟件系統,裏邊有不少模塊,每一個模塊的業務邏輯並非很固定。拿最近的項目,第三方支付,就是要和不一樣平臺交互,因此每一個模塊要常常改動,特別是測試階段,若是採用靜態庫,則每次整個系統都要從新編譯一次,不能保證系統24運行。可是,若是採起動態連接庫的方式,就能夠解決,每一個模塊下都有本身的makefile,編譯鏈接網,生成動態連接庫,並將其存放在LD_LIBRARY_PATH環境變量制定的目錄下。函數

 

 具體編譯步驟及命令工具

第1步:編輯獲得舉例的程序--hello.h、hello.c和main.c;

hello.h(見程序1)爲該函數庫的頭文件。

hello.c(見程序2)是函數庫的源程序,其中包含公用函數hello,該函數將在屏幕上輸出"Hello XXX!"。

main.c(見程序3)爲測試庫文件的主程序,在主程序中調用了公用函數hello。


 

 程序1: hello.h

 #ifndef HELLO_H
 #define HELLO_H
 
 void hello(const char *name);
 
 #endif //HELLO_H

 


 

 程序2: hello.c


 #include <stdio.h>
 
 void hello(const char *name)
 {
  printf("Hello %s!\n", name);
 }

 


 


  程序3: main.c
 #include "hello.h"
 
 int main()
 {
  hello("everyone");
  return 0;
 }

 


 

第2步:將hello.c編譯成.o文件;

不管靜態庫,仍是動態庫,都是由.o文件建立的。所以,咱們必須將源程序hello.c經過gcc先編譯成.o文件。

在系統提示符下鍵入如下命令獲得hello.o文件。

# gcc -c hello.c

#

(注1:本文不介紹各命令使用和其參數功能,若但願詳細瞭解它們,請參考其餘文檔。)

(注2:首字符"#"是系統提示符,不須要鍵入,下文相同。)

咱們運行ls命令看看是否生存了hello.o文件。

# ls

hello.c hello.h hello.o main.c

#

(注3:首字符不是"#"爲系統運行結果,下文相同。)

在ls命令結果中,咱們看到了hello.o文件,本步操做完成。

下面咱們先來看看如何建立靜態庫,以及使用它。


 

第3步:由.o文件建立靜態庫;

靜態庫文件名的命名規範是以lib爲前綴,緊接着跟靜態庫名,擴展名爲.a。例如:咱們將建立的靜態庫名爲myhello,則靜態庫文件名就是libmyhello.a。在建立和使用靜態庫時,須要注意這點。建立靜態庫用ar命令。

在系統提示符下鍵入如下命令將建立靜態庫文件libmyhello.a。

# ar cr libmyhello.a hello.o

#

咱們一樣運行ls命令查看結果:

# ls

hello.c hello.h hello.o libmyhello.a main.c

#

ls命令結果中有libmyhello.a。

#註釋:

格式:ar rcs  libxxx.a xx1.o xx2.o

參數r:在庫中插入模塊(替換)。當插入的模塊名已經在庫中存在,則替換同名的模塊。若是若干模塊中有一個模塊在庫中不存在,ar顯示一個錯誤消息,並不替換其餘同名模塊。默認的狀況下,新的成員增長在庫的結尾處,可使用其餘任選項來改變增長的位置。

參數c:建立一個庫。無論庫是否存在,都將建立。

參數s:建立目標文件索引,這在建立較大的庫時能加快時間。(補充:若是不須要建立索引,可改爲大寫S參數;若是.a文件缺乏索引,可使用ranlib命令添加)


 

第4步:在程序中使用靜態庫;

靜態庫製做完了,如何使用它內部的函數呢?只須要在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,而後在用gcc命令生成目標文件時指明靜態庫名,gcc將會從靜態庫中將公用函數鏈接到目標文件中。注意,gcc會在靜態庫名前加上前綴lib,而後追加擴展名.a獲得的靜態庫文件名來查找靜態庫文件。

在程序3:main.c中,咱們包含了靜態庫的頭文件hello.h,而後在主程序main中直接調用公用函數hello。下面先生成目標程序hello,而後運行hello程序看看結果如何。

# gcc -o hello main.c -L. -lmyhello      #註釋:"-L."必須加上,含義是指定連接庫所在的目錄,不然報錯,找不到對應的連接庫

# ./hello

Hello everyone!

#

咱們刪除靜態庫文件試試公用函數hello是否真的鏈接到目標文件 hello中了。

# rm libmyhello.a

rm: remove regular file `libmyhello.a'? y

# ./hello

Hello everyone!

#

程序照常運行,靜態庫中的公用函數已經鏈接到目標文件中了。

咱們繼續看看如何在Linux中建立動態庫。咱們仍是從.o文件開始。


 

第5步:由.o文件建立動態庫文件;

動態庫文件名命名規範和靜態庫文件名命名規範相似,也是在動態庫名增長前綴lib,但其文件擴展名爲.so。例如:咱們將建立的動態庫名爲myhello,則動態庫文件名就是libmyhello.so。用gcc來建立動態庫。

在系統提示符下鍵入如下命令獲得動態庫文件libmyhello.so。

# gcc -c -fPIC hello.c -o hello.o

# gcc -shared -fPCI -o libmyhello.so hello.o  

#

 

咱們照樣使用ls命令看看動態庫文件是否生成。「PIC」命令行標記告訴GCC產生的代碼不要包含對函數和變量具體內存位置的引用,這是由於如今還沒法知道使用該消息代碼的應用程序會將它鏈接到哪一段內存地址空間。這樣編譯出的hello.o能夠被用於創建共享連接庫。創建共享連接庫只須要用GCC的」-shared」標記便可

# ls

hello.c hello.h hello.o libmyhello.so main.c

#註釋

 Linux下生成動態連接庫是否必須使用 -fPIC 的問題 :-fPIC 做用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code),因此在編譯動態庫所需的.o文件時,gcc -c -fPIC hello.c -o hello.o,這樣在gcc -shared -fPCI -o libmyhello.so hello.o,纔不會報錯。

 

在 Linux 下製做動態連接庫,「標準」 的作法是編譯成位置無關代碼(Position Independent Code,PIC),而後連接成一個動態連接庫。常常遇到的一個問題是 -fPIC 是否是必需,由於好像不加常常也能正常運行,只是建立 .so 的時候會有一個警告。

可執行文件在連接時就知道每一行代碼、每個變量會被放到線性地址空間的什麼位置,所以這些地址能夠都做爲常數寫到代碼裏面。對動態庫,這就不行了,這要等到加載時才知道。無非下面兩種方法:

  (1) 可重定位代碼(relocatable code):Windows DLL 以及不使用 -fPIC 的 Linux SO。

  生成動態庫時假定它被加載在地址 0 處。加載時它會被加載到一個地址(base),這時要進行一次重定位(relocation),把代碼、數據段中全部的地址加上這個 base 的值。這樣代碼運行時就能使用正確的地址了。

  (2) 位置無關代碼(position independent code):使用 -fPIC 的 Linux SO。

  這樣的代碼自己就能被放到線性地址空間的任意位置,無需修改就能正確執行。一般的方法是獲取指令指針(如 IA32 的 EIP 寄存器)的值,加上一個偏移獲得全局變量/函數的地址。

結論:把動態庫編譯成 PIC 只有好處沒有壞處,於是部分CPU要求用於生成動態庫的目標文件必須使用 -fPIC 編譯也合情合理了。

 


 

第6步:在程序中使用動態庫;

在程序中使用動態庫和使用靜態庫徹底同樣,也是在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,而後在用gcc命令生成目標文件時指明動態庫名進行編譯。咱們先運行gcc命令生成目標文件,再運行它看看結果。

# gcc -o hello main.c -L. -lmyhello

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

哦!出錯了。快看看錯誤提示,原來是找不到動態庫文件libmyhello.so。程序在運行時,會在/usr/lib和/lib等目錄中查找須要的動態庫文件。若找到,則載入動態庫,不然將提示相似上述錯誤而終止程序運行。咱們將文件libmyhello.so複製到目錄/usr/lib中,再試試。

(使用」-lmyhello」標記來告訴GCC驅動程序在鏈接階段引用共享函數庫libmyhello.so。」-L.」標記告訴GCC函數庫可能位於當前目錄。不然GNU鏈接器會查找標準系統函數目錄:它前後搜索1.elf文件的 DT_RPATH段—2.環境變量LD_LIBRARY_PATH—3./etc/ld.so.cache文件列表—4./lib/,/usr/lib目錄找到庫文件後將其載入內存,可是咱們生成的共享庫在當前文件夾下,並無加到上述的4個路徑的任何一箇中,所以,執行後會出現錯誤)

# mv libmyhello.so /usr/lib

# ./hello

Hello everyone!

#

成功了。這也進一步說明了動態庫在程序運行時是須要的。

另外:既然鏈接器會搜尋LD_LIBRARY_PATH所指定的目錄,那麼咱們能夠將這個環境變量設置成當前目錄:

先執行:

export LD_LIBRARY_PATH=$(pwd)

再執行:

./hello

成功!

最後:執行:  

ldconfig   /usr/zhsoft/lib     
    
  注:   當用戶在某個目錄下面建立或拷貝了一個動態連接庫,若想使其被系統共享,能夠執行一下"ldconfig   目錄名"這個命令.此命令的功能在於讓ldconfig將指定目錄下的動態連接庫被系統共享起來,意即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫.本例讓系統共享了/usr/zhsoft/lib目錄下的動態連接庫.該命令會重建/etc/ld.so.cache文件

成功!

能夠查看程序執行時調用動態庫的過程:

 

 


 

 

咱們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令徹底同樣,那當靜態庫和動態庫同名時,gcc命令會使用哪一個庫文件呢?抱着對問題必究到底的心情,來試試看。

先刪除 除.c和.h外的 全部文件,恢復成咱們剛剛編輯完舉例程序狀態。

# rm -f hello hello.o /usr/lib/libmyhello.so

# ls

hello.c hello.h main.c

#

在來建立靜態庫文件libmyhello.a和動態庫文件libmyhello.so。

# gcc -c hello.c

# ar cr libmyhello.a hello.o

# gcc -shared -fPCI -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

#

經過上述最後一條ls命令,能夠發現靜態庫文件libmyhello.a和動態庫文件libmyhello.so都已經生成,並都在當前目錄中。而後,咱們運行gcc命令來使用函數庫myhello生成目標文件hello,並運行程序 hello。

# gcc -o hello main.c -L. -lmyhello

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

從程序hello運行的結果中很容易知道,當靜態庫和動態庫同名時, gcc命令將優先使用動態庫。


 

Note:

①編譯參數解析

最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態鏈接庫(讓鏈接器生成T類型的導出符號表,有時候也生成弱鏈接W類型的導出符號),不用該標誌外部程序沒法鏈接。至關於一個可執行文件
-fPIC:表示編譯爲位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的因此動態載入時是經過代碼拷貝的方式來知足不一樣進程的須要,而不能達到真正代碼段共享的目的。
-L.:表示要鏈接的庫在當前目錄中
-ltest:編譯器查找動態鏈接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來肯定庫的名稱
LD_LIBRARY_PATH:這個環境變量指示動態鏈接器能夠裝載動態庫的路徑。
 固然若是有root權限的話,能夠修改/etc/ld.so.conf文件,而後調用 /sbin/ldconfig來達到一樣的目的,不過若是沒有root權限,那麼只能採用輸出LD_LIBRARY_PATH的方法了。

 

調用動態庫的時候有幾個問題會常常碰到,有時,明明已經將庫的頭文件所在目錄 經過 「-I」 include進來了,庫所在文件經過 「-L」參數引導,並指定了「-l」的庫名,但經過ldd命令察看時,就是死活找不到你指定連接的so文件,這時你要做的就是經過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。一般這樣作就能夠解決庫沒法連接的問題了。

 

②靜態庫連接時搜索路徑順序:

1. ld會去找GCC命令中的參數-L

2. 再找gcc的環境變量LIBRARY_PATH

3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的

 

③動態連接時、執行時搜索路徑順序:

1.  編譯目標代碼時指定的動態庫搜索路徑;

2.  環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;

3.  配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;

4. 默認的動態庫搜索路徑/lib;

5. 默認的動態庫搜索路徑/usr/lib。

  

④有關環境變量:

LIBRARY_PATH環境變量:指定程序靜態連接庫文件搜索路徑

LD_LIBRARY_PATH環境變量:指定程序動態連接庫文件搜索路徑

 

⑤附註:

做爲Linux程序開發員,最好對開發工具和資源的位置有個初步瞭解。下面簡要介紹一下主要的文件夾和應用程序。

1.應用程序(Applications)

應用程序一般都有固定的文件夾,系統通用程序放在/usr/bin,往後系統管理員在本地計算機安裝的程序一般放在/usr/local/bin或者/opt文件夾下。除了系統程序外,大部分我的用到的程序都放在/usr/local下,因此保持/usr的整潔十分重要。當升級或者重裝系統的時候,只要把/usr/local的程序備份一下就能夠了。

一些其餘的程序有本身特定的文件夾,好比X Window系統,一般安裝在/usr/X11中,或者/usr/X11R6。GNU的編譯器GCC,一般放置在/usr/bin或者/usr/local/bin中,不一樣的Linux版本可能位置稍有不一樣。

 

2.頭文件(Head Files)

在C語言和其餘語言中,頭文件聲明瞭系統函數和庫函數,而且定義了一些常量。對於C語言,頭文件基本上散落於/usr/include和它的子文件夾下。其餘的編程語言的庫函數分佈在編譯器定義的地方,好比在一些Linux版本中,X Window系統庫函數分佈在/usr/include/X11,GNU C++的庫函數分佈在/usr/include/g++。這些系統庫函數的位置對於編譯器來講都是「標準位置」,即編譯器可以自動搜尋這些位置。

若是想引用位於標準位置以外的頭文件,咱們須要在調用編譯器的時候加上-I標誌,來顯式的說明頭文件所在文件夾。好比,

 

$ gcc -I/usr/openwin/include hello.c

會告訴編譯器除了標準位置外,還要去/usr/openwin/include看看有沒有所需的頭文件。詳細狀況見編譯器的使用手冊(man gcc)。

 

3.庫函數(Library Files)

庫函數就是函數的倉庫,它們都通過編譯,重用性不錯。一般,庫函數相互合做,來完成特定的任務。好比操控屏幕的庫函數(cursers和ncursers庫函數),數據庫讀取庫函數(dbm庫函數)等。

系統調用的標準庫函數通常位於/lib以及/usr/lib。C編譯器(精確點說,鏈接器)須要知道庫函數的位置。默認狀況下,它只搜索標準C庫函數。

庫函數文件一般開頭字母是lib。後面的部分標示庫函數的用途(好比C庫函數用c標識, 數學庫函數用m標示),小數點後的後綴代表庫函數的類型:

.a 指靜態連接庫.so 指動態連接庫

去/usr/lib看一下,你會發現,庫函數都有動態和靜態兩個版本。

與頭文件同樣,庫函數一般放在標準位置,但咱們也能夠經過-L標識符,來添加新的搜索文件夾,-l指定特定的庫函數文件。

相關文章
相關標籤/搜索