咱們一般把一些公用函數製做成函數庫,供其它程序使用。html
函數庫分爲靜態庫和動態庫兩種。linux
1. 靜態函數庫數據庫
這類庫的名字通常是libxxx.a;利用靜態函數庫編譯成的文件比較大,由於整個 函數庫的全部數據都會被整合進目標代碼中,他的優勢就顯而易見了,即編譯後的執行程序不須要外部的函數庫支持,由於全部使用的函數都已經被編譯進去了。固然這也會成爲他的缺點,由於若是靜態函數庫改變了,那麼你的程序必須從新編譯。編程
2. 動態函數庫緩存
這類庫的名字通常是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候 並無被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫裏的相應函數,所以動態函數庫所產生的可執行文件比較小。因爲函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,因此程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,因此動態函數庫的升級比較方便。
linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib編程語言
靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將再也不須要該靜態庫。函數
動態庫在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行是才被載入,所以在程序運行時還須要動態庫存在。工具
本文主要經過舉例來講明在Linux中如何建立靜態庫和動態庫,以及使用它們。post
在建立函數庫前,咱們先來準備舉例用的源程序,並將函數庫的源程序編譯成.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,能夠看到它是如何調用動態庫中的函數的。
[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 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選項添加標準函數庫以外的函數庫。