原文出處:http://hi.baidu.com/xiaoyue1800/item/416e9f110d8e3f9599ce33d2
我們通常把一些公用函數製作成函數庫,供其它程序使用。函數庫分爲靜態庫和動態庫兩種。
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。動態庫在程
序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需
要動態庫存在。本文主要通過舉例來說明在 Linux中如何創建靜態庫和動態庫,以及使用它
們。
第1步:編輯得到舉例的程序--hello.c和 test.c;
測試程序test.c調用了公用函數 my_lib_function。
hello.c:
#include <stdio.h>
void my_lib_function()
{
printf("library routine called\n");
}
test.c:
int main()
{
my_lib_function();
return 0;
}
第2步:將hello.c編譯成.o文件;
無論靜態庫,還是動態庫,都是由.o 文件創建的(動態庫可以直接通過.c)。因此,我們必須
將源程序hello.c通過 gcc先編譯成.o文件。
在系統提示符下鍵入以下命令得到 hello.o文件。
# gcc -c hello.c
第3步:由.o文件創建靜態庫;
靜態庫文件名的命名規範是以 lib爲前綴,緊接着跟靜態庫名,擴展名爲.a。
例如:我們將創建的靜態庫名爲 myhello,則靜態庫文件名就是 libmyhello.a。在創建和使用
靜態庫時,需要注意這點。創建靜態庫用ar命令。
在系統提示符下鍵入以下命令將創建靜態庫文件libmyhello.a。
# ar crv libmyhello.a hello.o
第4步:在程序中使用靜態庫;
靜態庫製作完了,如何使用它內部的函數呢?只需要在使用到這些公用函數的源程序中包含
這些公用函數的原型聲明,然後在用 gcc 命令生成目標文件時指明靜態庫名,gcc 將會從靜
態庫中將公用函數連接到目標文件中。注意,gcc會在靜態庫名前加上前綴 lib,然後追加擴
展名.a得到的靜態庫文件名來查找靜態庫文件。
# gcc -o test test.c -L. -lmyhello
# ./test
library routine called
我們刪除靜態庫文件試試公用函數 hello是否真的連接到目標文件 hello中了。
# rm libmyhello.a
# ./test
library routine called
程序照常運行,靜態庫中的公用函數已經連接到目標文件中了。
我們繼續看看如何在Linux中創建動態庫。我們還是從.o文件開始。
第5步:由.o文件創建動態庫文件;
動態庫文件名命名規範和靜態庫文件名命名規範類似,也是在動態庫名增加前綴 lib,但其
文件擴展名爲.so。
例如:我們將創建的動態庫名爲 myhello,則動態庫文件名就是 libmyhello.so。用 gcc來創建
動態庫。
在系統提示符下鍵入以下命令得到動態庫文件libmyhello.so。
# gcc -shared -fPCI -o libmyhello.so hello.o
也可以由.c文件直接創建動態庫:gcc -shared -fPCI -o libmyhello.so hello.c
我們照樣使用ls命令看看動態庫文件是否生成。
第6步:在程序中使用動態庫;
在程序中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函數的源程序中包含這
些公用函數的原型聲明,然後在用 gcc命令生成目標文件時指明動態庫名進行編譯。我們先
運行gcc命令生成目標文件,再運行它看看結果。
# gcc -o test test.c -L. -lmyhello
# ./test
./test: 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 中,再試試。
# mv libmyhello.so /usr/lib
# ./test
library routine called
成功了。這也進一步說明了動態庫在程序運行時是需要的。
如果出現:error while loading shared libraries: libmyhello.so: cannot restore segment prot after
reloc: Permission denied 錯誤原因在於selinux禁用了訪問此共享庫
解決辦法:關閉selinux
1、編輯/etc/selinux/config文件,找到SELINUX=enforcing,改爲 SELINUX=disabled
2、編輯/etc/sysconfig/selinux 文件,找到SELINUX=enforcing,改爲 SELINUX=disabled
3、重啓電腦
第7步:繼續;
我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令完全一樣,
那當靜態庫和動態庫同名時,gcc命令會使用哪個庫文件呢?抱着對問題必究到底的心情,
來試試看。
先刪除其他文件,只留 hello.c test.c hello.o
# rm -f test /usr/lib/libmyhello.so
在來創建靜態庫文件libmyhello.a 和動態庫文件libmyhello.so。
# ar cr libmyhello.a hello.o
# gcc –shared –fPCI -o libmyhello.so hello.o
# ls
hello.c hello.o libmyhello.a libmyhello.so test.c
通過上述最後一條 ls命令,可以發現靜態庫文件 libmyhello.a 和動態庫文件 libmyhello.so 都
已經生成,並都在當前目錄中。
# gcc -o test test.c -L. -lmyhello
# ./test
./test: 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 文件來指定動態庫的目錄。通常這樣做就可以解決庫無
法鏈接的問題了。
今天要用到靜態庫和動態庫,於是寫了幾個例子來鞏固一下基礎。
hello1.c
————————————————————
#include <stdio.h>
void print1(int i)
{
int j;
for(j=0;j<i;j++)
{
printf("%d * %d = %d\n",j,j,j*j);
}
}
hello2.c
_________________________________________________
#include <stdio.h>
void print2(char *arr)
{
char c;
int i=0;
while((c=arr[i++])!='\0')
{
printf("%d****%c\n",i,c);
}
}
hello.c
____________________________________________________
void print1(int);
void print2(char *);
int main(int argc,char **argv)
{
int i=100;
char *arr="THIS IS LAYMU'S HOME!";
print1(i);
print2(arr);
return 0;
}
可以看到hello.c要用到hello1.c中的print1函數和hello2.c中的print2函數。所以可以把這兩個函數組合爲庫,以供更多的程序作爲組件來調用。
方法一:將hello1.c和hello2.c編譯成靜態鏈接庫.a
[root@localhost main5]#gcc -c hello1.c hello2.c //將hello1.c和hello2.c分別編譯爲hello1.o和hello2.o,其中-c選項意爲只編譯不鏈接。
[root@localhost main5]#ar -r libhello.a hello1.o hello2.o //將hello1.o和hello2.o組合爲libhello.a這個靜態鏈接庫
[root@localhost main5]#cp libhello.a /usr/lib //將libhello.a拷貝到/usr/lib目錄下,作爲一個系統共享的靜態鏈接庫
[root@localhost main5]#gcc -o hello hello.c -lhello //將hello.c編譯爲可執行程序hello,這個過程用到了-lhello選項,這個選項告訴gcc編譯器到/usr/lib目錄下去找libhello.a的靜態鏈接庫
以上的過程類似於windows下的lib靜態鏈接庫的編譯及調用過程。
方法二:將hello1.o和hello2.o組合成動態鏈接庫.so
[root@localhost main5]#gcc -c -fpic hello1.c hello2.c //將hello1.c和hello2.c編譯成hello1.o和hello2.o,-c意爲只編譯不鏈接,-fpic意爲位置獨立代碼,指示編譯程序生成的代碼要適合共享庫的內容這樣的代碼能夠根據載入內存的位置計算內部地址。
[root@localhost main5]#gcc -shared hello1.o hello2.o -o hello.so //將hello1.o和hello2.o組合爲shared object,即動態鏈接庫
[root@localhost main5]#cp hello.so /usr/lib //將hello.so拷貝到/usr/lib目錄下
[root@localhost main5]#gcc -o hello hello.c hello.so //將hello.c編譯鏈接爲hello的可執行程序,這個過程用到了動態鏈接庫hello.so
在這裏要廢話幾句,其實一切的二進制信息都有其運作的機制,只要弄清楚了它的機制,並能夠實現之,則任何此時此刻無法想象之事都將成爲現實。當然,這兩者之間的巨大鴻溝需要頂級的設計思想和頂級的代碼來跨越。