靜態庫與動態庫

一、什麼是靜態庫、動態庫???
靜態庫:這類庫的名字一般是libxxx.a,xxx爲庫的名字。利用靜態函數庫編譯成的文件比較大,因爲整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進去了。當然這也會成爲他的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯。
動態庫:這類庫的名字一般是libxxx.M.N.so,同樣的xxx爲庫的名字,M是庫的主版本號,N是庫的副版本號。當然也可以不要版本號,但名字必須有。相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫裏的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。
動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib。當要使用靜態的程序庫時,連接器會找出程序所需的函數,然後將它們拷貝到執行文件,由於這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記指明當程序執行時,首先必須載入這個庫。由於動態庫節省空間,linux下進行連接的缺省操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。
二、如何創建及使用靜態庫、動態庫
2.1、靜態鏈接庫
製作靜態鏈接庫的過程中要用到gcc和ar命令。
ar命令:用來維護鏈接編輯庫所使用的庫索引。ar命令將一個或多個指定的文件併入單個寫成ar壓縮文檔格式的壓縮文檔文件。當ar命令創建庫的時候,它創建可傳輸格式的報頭;當他創建或者更新庫的時候,它重建符號表。
常用選項:
-c:創建一個庫,不管這個庫存不存在,都將創建。
-r:在庫中插入模塊(替換)。若插入的模塊在庫中存在,則會進行替換。
-s:創建目標文件索引,在創建較大的庫時能加快時間。不困是否修改了庫內容,都將重新符號表。
-t:將庫目錄寫到標準輸出,如果指定文件名稱,則顯示指定的文件。如果不指定文件名稱的話,則顯示庫目錄的所有文件。
-v:將建立新庫的詳細的文件逐個寫到標準輸出
例:準備兩個庫的源碼文件st1.c和st2.c,用它們來製作庫libmytest.a,如下:
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

靜態庫文件libmytest.a已經生成,用file命令查看其屬性,發現它確實是歸檔壓縮文件。用ar -t libmytest.a可以查看一個靜態庫包含了那些obj文件:
這裏寫圖片描述
接下來我們就寫個測試程序來調用庫libmytest.a中所提供的兩個接口print1()和print2()。
這裏寫圖片描述
在gcc -o test test.c -L. -lmytest 中,-lmytest表示要使用的庫,“-L.”表示這個庫再當前目錄下,-L表示定製目錄。
靜態庫鏈接時搜索路徑順序:
1. ld會去找GCC命令中的參數-L
2. 再找gcc的環境變量LIBRARY_PATH
3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的
2.2、動態庫
栗子:我們有一個頭文件test.h和2個源文件test1.c、test2.c將他們製作成一個名爲libtest.so的動態鏈接庫文件:
這裏寫圖片描述
創建動態庫:
-shared:該選項指定生成動態連接庫。
-fPIC:表示編譯爲位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
這裏寫圖片描述
動態鏈接庫的使用有兩種方法:既可以在運行時動態鏈接,也可以在代碼中通過函數進行動態加載。
1、動態鏈接:
使用“-lmytest”標記來告訴GCC驅動程序在連接階段引用共享函數庫libmytest.so。“-L.”標記告訴GCC函數庫可能位於當前目錄。否則GNU連接器會查找標準系統函數目錄。

這裏寫圖片描述

ldd可以查看一個可執行程序依賴的文件。這裏我們注意,ldd的輸出它說我們的libmytest.so它沒找到。這是爲什麼呢???
這個問題就與動態庫鏈接、執行時搜索路徑有關:
1. 編譯目標代碼時指定的動態庫搜索路徑
2. 環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑
3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑
4. 默認的動態庫搜索路徑/lib
5. 默認的動態庫搜索路徑/usr/lib
Linux系統中動態鏈接庫的配置文件一般在/etc/ld.so.conf文件內,它裏面存放的內容是可以被Linux共享的動態聯庫所在的目錄的名字。

然後/etc/ld.so.conf.d/目錄下存放了很多*.conf文件,如下:

[ym@www ActivityLib]$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf

其中每個conf文件代表了一種應用的庫配置內容。
在/etc目錄下還存在一個名叫ld.so.cache的文件。它是動態鏈接庫的緩存文件。爲了使得動態鏈接庫可以被系統使用,當我們修該了/etc/ld.so.conf或/etc/ld.so.conf.d/目錄下的任何文件,或者往那些目錄下拷貝了新的動態鏈接庫文件時,都需要運行一個很重要的命令:ldconfig,該命令位於/sbin目錄下,主要的用途就是負責搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf裏所列的目錄下搜索可用的動態鏈接庫文件,然後創建處動態加載程序/lib/ld-linux.so.2所需要的連接和(默認)緩存文件/etc/ld.so.cache(此文件裏保存着已經排好序的動態鏈接庫名字列表)。
也就是說:當用戶在某個目錄下面創建或拷貝了一個動態鏈接庫,若想使其被系統共享,可以執行一下”ldconfig目錄名”這個命令。此命令的功能在於讓ldconfig將指定目錄下的動態鏈接庫被系統共享起來,即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫。
因此要解決上面的問題的話,我們自己開發的共享庫就可以將其拷貝到/lib、/etc/lib目錄裏,又或者修改/etc/ld.so.conf文件將我們自己的庫路徑添加到該文件中,再執行ldconfig命令。
在這裏我將路徑拷貝到/etc/ld.so.conf文件內。
這裏寫圖片描述
2、動態加載
用一組函數來完成,需要包含頭文件:dlfcn.h.
相關函數:
const char *dlerror(void):當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值爲NULL時表示操作函數執行成功。
void *dlopen(const char *filename, int flag):
功能:用於打開指定名字(filename)的動態鏈接庫,並返回操作句柄。調用失敗時,將返回NULL值,否則返回的是操作句柄。
參數:
filename:如果名字不以“/”開頭,則非絕對路徑名,將按下列先後順序查找該文件。
(1)用戶環境變量中的LD_LIBRARY值;
(2)動態鏈接緩衝文件/etc/ld.so.cache
(3)目錄/lib,/usr/lib
flag:表示在什麼時候解決未定義的符號(調用)。取值有兩個:
1) RTLD_LAZY : 表明在動態鏈接庫的函數代碼執行時解決。
2) RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號,一旦未解決,dlopen將返回錯誤。
void *dlsym(void *handle, char *symbol):
功能: 根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。由此地址,可以帶參數執行相應的函數。
int dlclose (void *handle):
功能:用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數爲0時,纔會真正被系統卸載。2.2在程序中使用動態鏈接庫函數。

#include"test.h"
#include<stdio.h>
#include<dlfcn.h>

void (*fun)();

int main()
{
    void* handle = dlopen("./libmytest.so",RTLD_LAZY);
    const char* err = dlerror();
    if(NULL != err){
        perror("dlopen");
    }
    fun = dlsym(handle,"Print1");
    fun();
    fun = dlsym(handle,"Print2");
    fun();
    return 0;
}

這裏寫圖片描述

使用動態鏈接庫,源程序中要包含dlfcn.h頭文件,寫程序時注意dlopen等函數的正確調用,編譯時要採用-rdynamic選項與-ldl選項(不然編譯無法通過),以產生可調用動態鏈接庫的執行代碼。

發佈了47 篇原創文章 · 獲贊 21 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章