linux 中.a和.so的區別
本文轉載自http://www.cnblogs.com/laojie4321/archive/2012/03/28/2421056.html
感謝原作者的精彩分享
函式庫分為靜態庫和動態庫兩種。
1. 靜態函式庫
這類庫的名字一般是libxxx.a;利用靜態函式庫編譯成的檔案比較大,因為整個 函式庫的所有資料都會被整合進目的碼中,他的優點就顯而易見了,即編譯後的執行程式不需要外部的函式庫支援,因為所有使用的函式都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函式庫改變了,那麼你的程式必須重新編譯。
2. 動態函式庫
這類庫的名字一般是libxxx.so;相對於靜態函式庫,動態函式庫在編譯的時候 並沒有被編譯進目的碼中,你的程式執行到相關函式時才呼叫該函式庫裡的相應函式,因此動態函式庫所產生的可執行檔案比較小。由於函式庫沒有被整合進你的程式,而是程式執行時動態的申請並呼叫,所以程式的執行環境中必須提供相應的庫。動態函式庫的改變並不影響你的程式,所以動態函式庫的升級比較方便。
linux系統有幾個重要的目錄存放相應的函式庫,如/lib /usr/lib
靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。
動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式執行時還需要動態庫存在。
本文主要通過舉例來說明在Linux中如何建立靜態庫和動態庫,以及使用它們。
在建立函式庫前,我們先來準備舉例用的源程式,並將函式庫的源程式編譯成.o檔案。
第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。
第4步:在程式中使用靜態庫;
靜態庫製作完了,如何使用它內部的函式呢?只需要在使用到這些公用函式的源程式中包含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明靜態庫名,gcc將會從靜態庫中將公用函式連線到目標檔案中。注意,gcc會在靜態庫名前加上字首lib,然後追加副檔名.a得到的靜態庫檔名來查詢靜態庫檔案。
在程式3:main.c中,我們包含了靜態庫的標頭檔案hello.h,然後在主程式main中直接呼叫公用函式hello。下面先生成目標程式hello,然後執行hello程式看看結果如何。
# gcc -o hello main.c -L. -lmyhello
# ./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 -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
#
第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檔案
成功!
可以檢視程式執行時呼叫動態庫的過程:
# ldd hello
執行 test,可以看到它是如何呼叫動態庫中的函式的。
[[email protected] 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 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)。
庫函式(Library Files)
庫函式就是函式的倉庫,它們都經過編譯,重用性不錯。通常,庫函式相互合作,來完成特定的任務。比如操控螢幕的庫函式(cursers和ncursers庫函式),資料庫讀取庫函式(dbm庫函式)等。
系統呼叫的標準庫函式一般位於/lib以及/usr/lib。C編譯器(精確點說,聯結器)需要知道庫函式的位置。預設情況下,它只搜尋標準C庫函式。
庫函式檔案通常開頭字母是lib。後面的部分標示庫函式的用途(比如C庫函式用c標識, 數學庫函式用m標示),小數點後的字尾表明庫函式的型別:
.a 指靜態連結庫.so 指動態連結庫
去/usr/lib看一下,你會發現,庫函式都有動態和靜態兩個版本。
與標頭檔案一樣,庫函式通常放在標準位置,但我們也可以通過-L識別符號,來新增新的搜尋資料夾,-l指定特定的庫函式檔案。比如
$ gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
上述命令就會在編譯期間,連結位於/usr/openwin/lib資料夾下的libX11函式庫,編譯生成x11fred。
靜態連結庫(Static Libraries)
最簡單的函式庫就是一些函式的簡單集合。呼叫庫函式中的函式時,需要在呼叫函式中include定義庫函式的標頭檔案。我們用-l選項新增標準函式庫之外的函式庫。