天天看點

gcc生成.a靜态庫和.so動态庫

我們通常把一些公用函數制作成函數庫,供其它程式使用。函數庫分為靜态庫和動态庫兩

種。靜态庫在程式編譯時會被連接配接到目标代碼中,程式運作時将不再需要該靜态庫。動态

庫在程式編譯時并不會被連接配接到目标代碼中,而是在程式運作是才被載入,是以在程式運

行時還需要動态庫存在。本文主要通過舉例來說明在Linux中如何建立靜态庫和動态庫,以

及使用它們。

在建立函數庫前,我們先來準備舉例用的源程式,并将函數庫的源程式編譯成.o檔案。

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

hello.c(見程式2)是函數庫的源程式,其中包含公用函數hello,該函數将在螢幕上輸出"

Hello XXX!"。hello.h(見程式1)為該函數庫的頭檔案。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通過g

cc先編譯成.o檔案。

在系統提示符下鍵入以下指令得到hello.o檔案。

# gcc -c hello.c

#

我們運作ls指令看看是否生存了hello.o檔案。

# ls

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

#

在ls指令結果中,我們看到了hello.o檔案,本步操作完成。

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

第3步:由.o檔案建立靜态庫;

靜态庫檔案名的命名規範是以lib為字首,緊接着跟靜态庫名,擴充名為.a。例如:我們将

建立的靜态庫名為myhello,則靜态庫檔案名就是libmyhello.a。在建立和使用靜态庫時,

需要注意這點。建立靜态庫用ar指令。

在系統提示符下鍵入以下指令将建立靜态庫檔案libmyhello.a。

# ar -crv libmyhello.a hello.o

#

我們同樣運作ls指令檢視結果:

# ls

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

#

ls指令結果中有libmyhello.a。

第4步:在程式中使用靜态庫;

靜态庫制作完了,如何使用它内部的函數呢?隻需要在使用到這些公用函數的源程式中包

含這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明靜态庫名,gcc将會從

靜态庫中将公用函數連接配接到目标檔案中。注意,gcc會在靜态庫名前加上字首lib,然後追

加擴充名.a得到的靜态庫檔案名來查找靜态庫檔案。

在程式3:main.c中,我們包含了靜态庫的頭檔案hello.h,然後在主程式main中直接調用公

用函數hello。下面先生成目标程式hello,然後運作hello程式看看結果如何。

法一 # gcc -o hello main.c -L. -lmyhello,自定義的庫時,main.c還可放在-L.和 -lmyhello之間,但是不能放在它倆之後,否則會提示myhello沒定義,但是是系統的庫時,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出錯。

法二 #gcc main.c libmyhello.a -o hello

法三:先生成main.o:gcc -c main.c ,再生成可執行檔案:gcc -o hello main.o libmyhello.a,動态庫連接配接時也可以這樣做。

# ./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,則動态庫檔案名就是libmyh

ello.so。用gcc來建立動态庫。

在系統提示符下鍵入以下指令得到動态庫檔案libmyhello.so。

# gcc -shared -fPCI -o libmyhello.so hello.o (-o不可少)

#

我們照樣使用ls指令看看動态庫檔案是否生成。

# ls

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

#

第6步:在程式中使用動态庫;

在程式中使用動态庫和使用靜态庫完全一樣,也是在使用到這些公用函數的源程式中包含

這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明動态庫名進行編譯。我們

先運作gcc指令生成目标檔案,再運作它看看結果。

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

(或 #gcc main.c libmyhello.so -o hello 不會出錯(沒有libmyhello.so的話,會出錯),但是接下來./hello 會提示出錯,因為雖然連接配接時用的是目前目錄的動态庫,但是運作時,是到/usr/lib中找庫檔案的,将檔案libmyhello.so複制到目錄/usr/lib中就OK了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar

ed object file: No such file or directory

#

哦!出錯了。快看看錯誤提示,原來是找不到動态庫檔案libmyhello.so。程式在運作時,

會在/usr/lib和/lib等目錄中查找需要的動态庫檔案。若找到,則載入動态庫,否則将提

示類似上述錯誤而終止程式運作。我們将檔案libmyhello.so複制到目錄/usr/lib中,再試

試。

# mv libmyhello.so /usr/lib

# ./hello

Hello everyone!

#

成功了。這也進一步說明了動态庫在程式運作時是需要的。

我們回過頭看看,發現使用靜态庫和使用動态庫編譯成目标程式使用的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 (或-cvr )

# 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.s

o都已經生成,并都在目前目錄中。然後,我們運作gcc指令來使用函數庫myhello生成目标

檔案hello,并運作程式 hello。

# gcc -o hello main.c -L. -lmyhello (動态庫和靜态庫同時存在時,優先使用動态庫, 當然,直接#gcc main.c libmyhello.a -o hello的話,就是指定為靜态庫了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar

ed object file: No such file or directory

#

從程式hello運作的結果中很容易知道,當靜态庫和動态庫同名時,gcc指令将優先使用動态庫,預設去連/usr/lib和/lib等目錄中的動态庫,将檔案libmyhello.so複制到目錄/usr/lib中即可。

Note:

編譯參數解析

最主要的是GCC指令行的一個選項:

-shared 該選項指定生成動态連接配接庫(讓連接配接器生成T類型的導出符号表,有時候也生成弱連接配接W類型的導出符号),不用該标志外部程式無法連接配接。相當于一個可執行檔案

-fPIC 表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的是以動态載入時是通過代碼拷貝的方式來滿足不同程序的需要,而不能達到真正代碼段共享的目的。

-L. 表示要連接配接的庫在目前目錄中;(多個庫:在編譯指令行中,将使用的靜态庫檔案放在源檔案後面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop

其中-L/usr/lib指定庫檔案的查找路徑。編譯器預設在目前目錄下先查找指定的庫檔案,如前面的"法二 #gcc main.c libmyhello.a -o hello")

-lmyhello 編譯器查找動态連接配接庫時有隐含的命名規則,即在給出的名字前面加上lib,後面加上.so或.a來确定庫的名稱libmyhello.so或libmyhello.a。

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檔案來指定動态庫的目錄。通常這樣做就可以解決庫無法連結的問題了。

另:

從上述可知,如何找到生成的動态庫有3種方式:

(1)把庫拷貝到/usr/lib和/lib目錄下。

(2)在LD_LIBRARY_PATH環境變量中加上庫所在路徑。

例如動态庫libhello.so在/home/example/lib目錄下:

$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib

(3) 修改/etc/ld.so.conf檔案,把庫所在的路徑加到檔案末尾,并執行ldconfig重新整理。這樣,加入的目錄下的所有庫檔案都可見。

附:像下面這樣指定路徑去連接配接系統的靜态庫,會報錯說要連接配接的庫找不到:

g++ -o main main.cpp -L/usr/lib libpthread.a

必須這樣g++ -o main main.cpp -L/usr/lib -lpthread才正确 。

自定義的庫考到/usr/lib 下時,

g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 會出錯,但是這樣 g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass 就正确了。