Linux下Gcc生成和使用靜態庫和動態庫詳解


1、基本概念
linux

1.1什麼是庫windows

在windows平臺和linux平臺下都大量存在着庫。緩存

本質上來講庫是一種可執行代碼的二進制形式,能夠被操做系統載入內存執行。函數

因爲windows和linux的平臺不一樣(主要是編譯器、彙編器和鏈接器的不一樣),所以兩者庫的二進制是不兼容的。工具

本文僅限於介紹linux下的庫。測試

 

 

1.2庫的種類google

linux下的庫有兩種:靜態庫和共享庫(動態庫)。spa

兩者的不一樣點在於代碼被載入的時刻不一樣。操作系統

靜態庫的代碼在編譯過程當中已經被載入可執行程序,所以體積較大。命令行

共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程當中僅簡單的引用,所以代碼體積較小。

 

 

1.3庫存在的意義

庫是別人寫好的現有的,成熟的,能夠複用的代碼,你可使用但要記得遵照許可協議。

現實中每一個程序都要依賴不少基礎的底層庫,不可能每一個人的代碼都從零開始,所以庫的存在乎義非同尋常。

共享庫的好處是,不一樣的應用程序若是調用相同的庫,那麼在內存裏只須要有一份該共享庫的實例。

 

 

1.4庫文件是如何產生的在linux下

靜態庫的後綴是.a,它的產生分兩步

Step 1.由源文件編譯生成一堆.o,每一個.o裏都包含這個編譯單元的符號表

Step 2.ar命令將不少.o轉換成.a,成爲靜態庫

動態庫的後綴是.so,它由gcc加特定參數編譯產生。

具體方法參見後文實例。

 

1.5庫文件是如何命名的,有沒有什麼規範

在linux下,庫文件通常放在/usr/lib和/lib下,

靜態庫的名字通常爲libxxxx.a,其中xxxx是該lib的名稱

動態庫的名字通常爲libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號

 

 

1.6如何知道一個可執行程序依賴哪些庫

ldd命令能夠查看一個可執行程序依賴的共享庫,

例如# ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

能夠看到ln命令依賴於libc庫和ld-linux庫

 

 

1.7可執行程序在執行的時候如何定位共享庫文件

當系統加載可執行代碼時候,可以知道其所依賴的庫的名字,可是還須要知道絕對路徑。

此時就須要系統動態載入器(dynamic linker/loader)

對於elf格式的可執行程序,是由ld-linux.so*來完成的,它前後搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄找到庫文件後將其載入內存

如:export LD_LIBRARY_PATH=’pwd’

將當前文件目錄添加爲共享目錄

 

1.8在新安裝一個庫以後如何讓系統可以找到他

若是安裝在/lib或者/usr/lib下,那麼ld默認可以找到,無需其餘操做。

若是安裝在其餘目錄,須要將其添加到/etc/ld.so.cache文件中,步驟以下

1.編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑

2.運行ldconfig,該命令會重建/etc/ld.so.cache文件

 

2、用gcc生成靜態和動態連接庫的示例

咱們一般把一些公用函數製做成函數庫,供其它程序使用。

函數庫分爲靜態庫和動態庫兩種。

 

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

 

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

 

本文主要經過舉例來講明在Linux中如何建立靜態庫和動態庫,以及使用它們。

 

爲了便於闡述,咱們先作一部分準備工做。

 

2.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

 

程序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.2問題的提出

注意:這個時候,咱們編譯好的hello.o是沒法經過gcc –o 編譯的,這個道理很是簡單,

hello.c是一個沒有main函數的.c程序,所以不夠成一個完整的程序,若是使用gcc –o 編譯並鏈接它,GCC將報錯。

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

這個時候咱們有三種思路:

1)  經過編譯多個源文件,直接將目標代碼合成一個.o文件。

2)  經過建立靜態連接庫libmyhello.a,使得main函數調用hello函數時可調用靜態連接庫。

3)  經過建立動態連接庫libmyhello.so,使得main函數調用hello函數時可調用靜態連接庫。

2.3思路一:編譯多個源文件

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

# gcc -c hello.c

爲何不使用gcc–o hello hello.cpp 這個道理咱們以前已經說了,使用-c是什麼意思呢?這涉及到gcc 編譯選項的常識。

 

咱們一般使用的gcc –o 是將.c源文件編譯成爲一個可執行的二進制代碼(-o選項實際上是制定輸出文件文件名,若是不加-c選項,gcc默認會編譯鏈接生成可執行文件,文件的名稱有-o選項指定),這包括調用做爲GCC內的一部分真正的C編譯器(ccl),以及調用GNU C編譯器的輸出中實際可執行代碼的外部GNU彙編器(as)和鏈接器工具(ld)。

gcc –c是使用GNU彙編器將源文件轉化爲目標代碼以後就結束,在這種狀況下,只調用了C編譯器(ccl)和彙編器(as),而鏈接器(ld)並無被執行,因此輸出的目標文件不會包含做爲Linux程序在被裝載和執行時所必須的包含信息,但它能夠在之後被鏈接到一個程序。

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

# ls

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

 

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

 

同理編譯main

#gcc –c main.c

 

將兩個文件連接成一個.o文件。

#gcc –o hello hello.o main.o

 

運行

# ./hello

 

Hello everyone!

 

完成^ ^!

2.4思路二:靜態連接庫

 

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

 

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

 

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

 

# ar rcs libmyhello.a hello.o

 

 

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

 

# ls

 

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

 

ls命令結果中有libmyhello.a

 

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

 

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

 

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

 

# ./hello

 

Hello everyone!

 

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

 

# rm libmyhello.a

 

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

 

# ./hello

 

Hello everyone!

 

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

靜態連接庫的一個缺點是,若是咱們同時運行了許多程序,而且它們使用了同一個庫函數,這樣,在內存中會大量拷貝同一庫函數。這樣,就會浪費不少珍貴的內存和存儲空間。使用了共享連接庫的Linux就能夠避免這個問題。

共享函數庫和靜態函數在同一個地方,只是後綴有所不一樣。好比,在一個典型的Linux系統,標準的共享數序函數庫是/usr/lib/libm.so。

當 一個程序使用共享函數庫時,在鏈接階段並不把函數代碼鏈接進來,而只是連接函數的一個引用。當最終的函數導入內存開始真正執行時,函數引用被解析,共享函 數庫的代碼才真正導入到內存中。這樣,共享連接庫的函數就能夠被許多程序同時共享,而且只需存儲一次就能夠了。共享函數庫的另外一個優勢是,它能夠獨立更 新,與調用它的函數絕不影響。

2.5思路3、動態連接庫(共享函數庫)

 

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

 

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

 

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

 

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

 

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

 

咱們照樣使用ls命令看看動態庫文件是否生成。

 

# ls

 

hello.cpp hello.h hello.o libmyhello.so main.cpp

調用動態連接庫編譯目標文件。

 

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

 

若是直接用以下方法進行編譯,並鏈接:

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

 

(使用」-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個路徑的任何一箇中,所以,執行後 會出現錯誤)

 

# ./hello

 

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

 

#

錯誤提示,找不到動態庫文件libmyhello.so。程序在運行時,會在/usr/lib/lib等目錄中查找須要的動態庫文件。若找到,則載入動態庫,不然將提示相似上述錯誤而終止程序運行。有多種方法能夠解決,

(1)咱們將文件 libmyhello.so複製到目錄/usr/lib中,再試試。

 

# mv libmyhello.so /usr/lib

 

# ./hello

成功!

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

先執行:

export LD_LIBRARY_PATH=$(pwd)

再執行:

./hello

成功!

(3)

執行:  

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

 

成功!

¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

下面的這個錯誤我沒有遇到,不過也記錄下,給遇到的人:

 {  這步後我沒有成功,報錯內容以下:/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied

google了一下,發現是SELinux搞的鬼,解決辦法有兩個:

1.
    chcon -t texrel_shlib_t   
/usr/lib/libmyhello.so
    (chcon -t texrel_shlib_t "你不能share的庫的絕對路徑")

2.
    #vi /etc/sysconfig/selinux file
    或者用
    #gedit /etc/sysconfig/selinux file
    修改SELINUX=disabled
    重啓

}

#

¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

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

 

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

# ldd hello
執行 test,能夠看到它是如何調用動態庫中的函數的。
[pin@localhost 20090505]$ ldd hello
        linux-gate.so.1 => (0x00110000)
        libmyhello.so => /usr/lib/libmyhello.so (0x00111000)
        libc.so.6 => /lib/libc.so.6 (0x00859000)
        /lib/ld-linux.so.2 (0x0083a000)

咱們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程序使用的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 rcs 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 shar
ed object file: No such file or directory

#

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

Note:

編譯參數解析


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

相關文章
相關標籤/搜索