轉自:https://www.cnblogs.com/nufangrensheng/p/3578784.html
靜態鏈接庫
前言
靜態庫是obj文件的一個集合(目標文件中通常僅解析了文件內部的變量和函數,對於引用的函數和變量還沒有解析,這需要將其他已經編寫好的目標文件引用進來,將沒有解析的函數和變量進行解析,通常引用的目標是庫),通常靜態庫以".a"為后綴,名字格式一般為libxxx.a。靜態庫由程序ar生成。
實例程序如下:
Main.c
#include <stdio.h> extern void print_hello(); int main(void) { print_hello(); }
Print_hello.c
#include <stdio.h> void print_hello() { printf("hello\n"); }
-
生成靜態鏈接庫
創建靜態庫的步驟:
- 生成目標文件。(使用命令gcc –c file.c)
- 使用工具ar對目標文件進行歸檔。(使用的命令如下)
生成靜態鏈接庫,或者將一個obj文件加入到已經存在的靜態庫的命令格式為:
ar –rcs 庫文件obj_1 obj_2 …
使用上面的實例程序print_hello.c創建靜態鏈接庫:
-
使用靜態鏈接庫
使用方式一:
使用方式二:
注意,在方法二中"-L./"不可少,否則出現如下錯誤:
這是因為上面的命令在系統默認的路徑下查找hello函數庫,而我們並沒有將libhello.a庫放在系統默認搜索路徑下,所以需要顯示指定庫函數的路徑為當前目錄。
另外還需注意,在使用-l選項時,-o選項的目標名稱要在-l鏈接的庫名稱之前,否則gcc會認為-l是生成的目標而出錯。
下面兩個圖分別是頭文件和庫文件的默認搜索路徑:
動態鏈接庫
前言
動態鏈接庫是程序運行時加載的庫,當動態鏈接庫正確安裝后,所有的 程序都可以使用動態庫來運行程序。動態鏈接庫是目標文件的集合,目標文件在動態鏈接庫中的組織方式是按照特殊方式形成的。庫中函數和變量的地址是相對地址,不是絕對地址,其真實地址在調用動態庫的程序加載時形成。
動態鏈接庫的名稱有別名(soname)、真名(realname)和鏈接名(linker name):
別名:libxxx.so,這種形式的庫名正是執行編譯命令時編譯器要搜索的名字。
真名:動態鏈接庫的真實名稱,一般總是在別名的基礎上加上一個小版本號、發布版本等構成。
鏈接名:程序鏈接時使用的庫的名字。
-
生成動態鏈接庫
生成動態鏈接庫的命令很簡單,使用-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,放到其中任何一個目錄下都可以。
-
動態鏈接庫的配置
動態鏈接庫並不是可以隨意地使用,要在運行的 程序中使用動態鏈接庫,需要指定系統的動態鏈接庫搜索的路徑,讓系統找到運行所需要的動態鏈接庫才可以。
系統中的配置文件/etc/ld.so.conf是動態鏈接庫的搜索路徑配置文件。這這個文件內,存放着可被Linux共享的動態鏈接庫所在目錄的名字(系統目錄/lib,/usr/lib除外,這兩個目錄默認就是動態鏈接庫的搜索路徑),多個目錄名間以空白字符(空格、換行等)或冒號或逗號分割。查看系統中的動態鏈接庫配置文件的內容:
Linux的配置文件將目錄ld.so.conf.d/中的配置文件包含了進來。
-
動態鏈接庫管理命令
為了讓新增加的動態鏈接庫能夠被系統共享,需要運行動態鏈接庫的 管理命令ldconfig。ldconfig命令的作用是在系統的默認搜索路徑和動態鏈接庫配置文件中所列出的目錄里搜索動態鏈接庫,創建動態鏈接庫裝入程序需要的鏈接和緩存文件。搜索完畢后,將結果寫入緩存文件/etc/ld.so.cache中,文件中保存的是已經排好序的動態鏈接庫名字列表。
ldconfig命令的用法如下:
-
使用動態鏈接庫
把當前工作目錄(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中實現:
2)使用ld-Linux.so.2來加載程序,命令格式為:
/lib/ ld-Linux.so.2 –library-path 路徑 程序名
加載test程序的命令為:
不過貌似並不是所有系統上都有ld-Linux.so.2。
注意,如果系統的搜索路徑下同時存在靜態鏈接庫和動態鏈接庫,默認情況下會鏈接動態鏈接庫。如果需要強制鏈接靜態鏈接庫,需要加上"-satic"選項。
動態加載庫
前言
動態加載庫和一般的動態鏈接庫所不同的是,一般動態鏈接庫在程序啟動時就要尋找動態庫,找到庫函數;而動態加載庫可以用程序的方法來控制什么時候加載。動態加載庫主要有函數dlopen()、dlerror()、dlsym()和dlclose()來控制動態庫的使用。函數原型如下:
-
打開動態庫dlopen()
函數dlopen()按照用戶指定的方式打開動態鏈接庫,其中參數filename為動態鏈接庫的文件名,flag為打開方式,一般為RTLD_LASY,函數的返回值為庫的指針。
例如,下面的代碼使用dlopen打開當前目錄下的動態庫libhello.so:
void *phandle = dlopen("./libhello.so", RTLD_LAZY);
-
獲得函數指針dlsym()
使用動態鏈接庫的目的是調用其中的函數,完成特定的功能。函數dlsym()可以獲得動態鏈接庫中指定函數的指針,然后可以使用這個函數指針進行操作。其中參數handle為dlopen()打開動態庫后返回的句柄,參數symbol為函數的名稱,返回值為函數指針。
-
使用動態加載庫實例
#include <stdio.h> #include <dlfcn.h> int main(void) { 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"); perr = dlerror(); if(perr != NULL) { printf("%s\n", perr); return(0); } (*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選項。
如果我們加入了-lpthread選項,此時嘗試着把程序中的#include <pthread.h>去掉,重新編譯你會發現同樣也是可以的。
總結:如果使用的是標准庫中的函數,則只需將頭文件包含進來。如果使用的函數所在的庫不是標准庫(例如我們自己編寫的庫或標准以外擴展的庫),則在編譯時必須加入-lxxx選項,而頭文件則可以包含也可以不包含,包含進來顯得規范而已。