天天看點

靜态連結庫、動态連結庫和動态加載庫

靜态連結庫

前言

靜态庫是obj檔案的一個集合(目标檔案中通常僅解析了檔案内部的變量和函數,對于引用的函數和變量還沒有解析,這需要将其他已經編寫好的目标檔案引用進來,将沒有解析的函數和變量進行解析,通常引用的目标是庫),通常靜态庫以".a"為字尾,名字格式一般為libxxx.a。靜态庫由程式ar生成。

執行個體程式如下:

Main.c

#include <stdio.h>

extern void print_hello();

int

main(void)

{

    print_hello();

}

Print_hello.c

void

print_hello()

    printf("hello\n");

  1. 生成靜态連結庫

    建立靜态庫的步驟:
    1. 生成目标檔案。(使用指令gcc –c file.c)
    2. 使用工具ar對目标檔案進行歸檔。(使用的指令如下)

    生成靜态連結庫,或者将一個obj檔案加入到已經存在的靜态庫的指令格式為:

    ar –rcs 庫檔案obj_1 obj_2 …

    使用上面的執行個體程式print_hello.c建立靜态連結庫:

    靜态連結庫、動态連結庫和動态加載庫
  2. 使用靜态連結庫

    使用方式一:
    靜态連結庫、動态連結庫和動态加載庫
    使用方式二:
    靜态連結庫、動态連結庫和動态加載庫
    注意,在方法二中"-L./"不可少,否則出現如下錯誤:
    靜态連結庫、動态連結庫和動态加載庫

    這是因為上面的指令在系統預設的路徑下查找hello函數庫,而我們并沒有将libhello.a庫放在系統預設搜尋路徑下,是以需要顯示指定庫函數的路徑為目前目錄。

    另外還需注意,在使用-l選項時,-o選項的目标名稱要在-l連結的庫名稱之前,否則gcc會認為-l是生成的目标而出錯。

    下面兩個圖分别是頭檔案和庫檔案的預設搜尋路徑:

    靜态連結庫、動态連結庫和動态加載庫
    靜态連結庫、動态連結庫和動态加載庫

動态連結庫

動态連結庫是程式運作時加載的庫,當動态連結庫正确安裝後,所有的 程式都可以使用動态庫來運作程式。動态連結庫是目标檔案的集合,目标檔案在動态連結庫中的組織方式是按照特殊方式形成的。庫中函數和變量的位址是相對位址,不是絕對位址,其真實位址在調用動态庫的程式加載時形成。

    動态連結庫的名稱有别名(soname)、真名(realname)和連結名(linker name):

    别名:libxxx.so,這種形式的庫名正是執行編譯指令時編譯器要搜尋的名字。

    真名:動态連結庫的真實名稱,一般總是在别名的基礎上加上一個小版本号、釋出版本等構成。

    連結名:程式連結時使用的庫的名字。

  1. 生成動态連結庫

    生成動态連結庫的指令很簡單,使用-fPIC選項或者-fpic選項。-fPIC和-fpic選項的作用是使得gcc生成的代碼是位置無關的,例如下面的指令将print_hello.c編譯生成動态連結庫:
    靜态連結庫、動态連結庫和動态加載庫

    其中-shared選項告訴編譯器生成一個動态連結庫;-soname,libhello.so表示生成動态庫的别名是libhello.so;-o libhello.so.1選項擇表示生成名字為libhello.so.1的實際動态連結庫檔案。

    生成動态連結庫之後一個很重要的問題就是安裝,一般情況下将生成的動态連結庫複制到系統預設的動态連結庫的搜尋路徑下,通常有/lib,/usr/lib,/usr/local/lib,放到其中任何一個目錄下都可以。

  2. 動态連結庫的配置

    動态連結庫并不是可以随意地使用,要在運作的 程式中使用動态連結庫,需要指定系統的動态連結庫搜尋的路徑,讓系統找到運作所需要的動态連結庫才可以。

    系統中的配置檔案/etc/ld.so.conf是動态連結庫的搜尋路徑配置檔案。這這個檔案内,存放着可被Linux共享的動态連結庫所在目錄的名字(系統目錄/lib,/usr/lib除外,這兩個目錄預設就是動态連結庫的搜尋路徑),多個目錄名間以空白字元(空格、換行等)或冒号或逗号分割。檢視系統中的動态連結庫配置檔案的内容:

    靜态連結庫、動态連結庫和動态加載庫
    Linux的配置檔案将目錄ld.so.conf.d/中的配置檔案包含了進來。
  3. 動态連結庫管理指令

    為了讓新增加的動态連結庫能夠被系統共享,需要運作動态連結庫的 管理指令ldconfig。ldconfig指令的作用是在系統的預設搜尋路徑和動态連結庫配置檔案中所列出的目錄裡搜尋動态連結庫,建立動态連結庫裝入程式需要的連結和緩存檔案。搜尋完畢後,将結果寫入緩存檔案/etc/ld.so.cache中,檔案中儲存的是已經排好序的動态連結庫名字清單。

    ldconfig指令的用法如下:

    靜态連結庫、動态連結庫和動态加載庫
    靜态連結庫、動态連結庫和動态加載庫
    靜态連結庫、動态連結庫和動态加載庫
  4. 使用動态連結庫

把目前工作目錄(libhello.so.1所在的目錄)加入動态連結庫的搜尋路徑配置檔案/etc/ld.so.conf中:

靜态連結庫、動态連結庫和動态加載庫

執行ldconfig指令重新整理緩存檔案/etc/ld.so.cache:

靜态連結庫、動态連結庫和動态加載庫

我們會發現,執行ldconfig指令後,目前目錄下生成了一個新的檔案libhello.so(這是-soname,libhello.so選項導緻的,如果沒有該選項,那麼ldconfig執行後不會生成新檔案libhello.so。),使用ls指令會發現此檔案是libhello.so.1的連結檔案:

靜态連結庫、動态連結庫和動态加載庫

在編譯程式時,使用動态連結庫和靜态連結庫是一緻的,使用"-lxxx"的方式:

靜态連結庫、動态連結庫和動态加載庫

編譯後執行時出現如下錯誤:

靜态連結庫、動态連結庫和動态加載庫

其主要原因是使用了SELINUX。将其狀态設定為disabled即可,方法如下:

打開/etc/selinux/config,将selinux=enforcing或permissive改成disabled。然後存盤退出,重新開機系統。

再次執行,結果如下:

靜态連結庫、動态連結庫和動态加載庫

運作時,還可能出現這樣的錯誤:test: error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory,這是由于程式運作時沒有找到動态連結庫造成的。程式編譯時連結動态連結庫和運作時使用動态連結庫的概念是不同的,在運作時,程式連結的動态連結庫需要在系統目錄下才行。有幾種辦法可以解決此種問題:

  1. 将動态連結庫的目錄放到程式搜尋路徑中,可以将庫的路徑添加到環境變量LD_LIBRARY_PATH中實作:
靜态連結庫、動态連結庫和動态加載庫
  1. 使用ld-Linux.so.2來加載程式,指令格式為:

/lib/ ld-Linux.so.2 –library-path 路徑 程式名

加載test程式的指令為:

靜态連結庫、動态連結庫和動态加載庫

不過貌似并不是所有系統上都有ld-Linux.so.2。

注意,如果系統的搜尋路徑下同時存在靜态連結庫和動态連結庫,預設情況下會連結動态連結庫。如果需要強制連結靜态連結庫,需要加上"-satic"選項。

靜态連結庫、動态連結庫和動态加載庫

動态加載庫

    動态加載庫和一般的動态連結庫所不同的是,一般動态連結庫在程式啟動時就要尋找動态庫,找到庫函數;而動态加載庫可以用程式的方法來控制什麼時候加載。動态加載庫主要有函數dlopen()、dlerror()、dlsym()和dlclose()來控制動态庫的使用。函數原型如下:

靜态連結庫、動态連結庫和動态加載庫
  1. 打開動态庫dlopen()

    函數dlopen()按照使用者指定的方式打開動态連結庫,其中參數filename為動态連結庫的檔案名,flag為打開方式,一般為RTLD_LASY,函數的傳回值為庫的指針。

    例如,下面的代碼使用dlopen打開目前目錄下的動态庫libhello.so:

    void *phandle = dlopen("./libhello.so", RTLD_LAZY);

    靜态連結庫、動态連結庫和動态加載庫
  2. 獲得函數指針dlsym()

    使用動态連結庫的目的是調用其中的函數,完成特定的功能。函數dlsym()可以獲得動态連結庫中指定函數的指針,然後可以使用這個函數指針進行操作。其中參數handle為dlopen()打開動态庫後傳回的句柄,參數symbol為函數的名稱,傳回值為函數指針。
  3. 使用動态加載庫執行個體

    #include <dlfcn.h>

        void (*printhello)(void);

        void *phandle = NULL;

        char *perr = NULL;

        phandle = dlopen("./libhello.so", RTLD_LAZY);

        if(!phandle)

        {

            printf("Failed load library!\n");

        }

        perr = dlerror();

        if(perr != NULL)

            printf("%s\n", perr);

            return(0);

        printhello = dlsym(phandle, "print_hello");

        }    

        (*printhello)();

        dlclose(phandle);

        return(0);

    編譯運作:

    靜态連結庫、動态連結庫和動态加載庫
    注意,想要使用dlfcn.h中的函數,編譯時必須加上選項-ldl。

小發現:

  有沒有注意到,我們并沒有使用用到include <hello.h>(假設我們為print_hello.c寫了一個hello.h的頭檔案)。其實,隻要編譯指令中加入選項-lhello,hello.h頭檔案包含不包含都沒有問題。為了驗證這個問題,使用http://www.cnblogs.com/nufangrensheng/p/3518411.html中的程式清單11-1這個執行個體進行了測試:

  在http://www.cnblogs.com/nufangrensheng/p/3518411.html程式清單11-1編譯過程中,曾遇到undefined reference to ‘pthread_create’這樣的錯誤,原因在于沒有在編譯指令中加入-lpthread選項。

繼續閱讀