靜態函式庫
靜態函式庫(static library)就是由一些物件檔案(object files)所構成的封裝檔,通常其檔案名稱都會以 lib
開頭,而副檔名則為 .a
。
使用靜態連結函式庫的好處就是所有的程式都包裝在執行檔中,不會因為缺少函式庫的檔案而不能執行,不過缺點就是這樣的執行檔大小會比較大,而如果函式庫有更新的話,整個執行檔也要跟著重新編譯。
以下我們示範將 sum.c
的內容製作成一個靜態函式庫的步驟。
Step 1
編譯 sum.c
的程式碼:
gcc -c -o sum.o sum.c
Step 2
使用 ar
指令將 sum.o
打包成 libsum.a
這個靜態連結函式庫:
ar -rcs libsum.a sum.o
Step 3
使用 libsum.a
這個靜態連結函式庫:
gcc main.c -L. -lsum -o main_static
也可以直接把 libsum.a
這個靜態連結函式庫檔案放進去編譯:
gcc main.c libsum.a -o main_static
這樣就可以將 main.c
這個程式編譯成 main_static
這個執行檔,而在編譯的過程中就不需要 sum.c
這個原始碼的檔案。
使用靜態連結函式庫所編譯出來的執行檔可以獨立執行,不需要原本的 libsum.a
檔:
ldd main_static
linux-vdso.so.1 => (0x00007fffb0d1a000) libc.so.6 => /lib64/libc.so.6 (0x00007f0411e46000) /lib64/ld-linux-x86-64.so.2 (0x00007f0412220000)
共享函式庫
共享函式庫(shared library)是在程式實際開始執行時,才會被載入的函式庫,執行檔本身與共享函式庫是分離的,這樣可以讓執行檔的大小比較小,而且未來共享函式庫在更新之後,執行檔也不需要重新編譯,而缺點則是執行檔在執行時就會需要共享函式庫的檔案,如果缺少了共享函式庫的檔案,就會無法執行。
共享函式庫的檔案通常也是以 lib
開頭,但是其副檔名則為 .so
,以下是建立共享函式庫的步驟。
Step 1
編譯時加入 -fPIC
參數,產生共享函式庫所需要的 position independant code:
gcc -c -fPIC -o sum.o sum.c
Step 2
使用 gcc
建立共享函式庫:
gcc -shared -Wl,-soname,libsum.so.1 -o libsum.so.1.0.0 sum.o
這裡的 -Wl
是用來將一些參數設定傳給連結器(linker),所以之後的 -soname
等參數就是傳給連結器的參數。
-soname
指定為 libsum.so.1
是代表函式庫的名稱,以 lib
開頭,接著是自己取的名稱,最後加上 .so
與 version 版本號碼,這一個 version 版本號碼所代表的是函示庫的介面版本,如果介面有改變時就會增加 version 版本號碼,以維護相容性的問題。
而最後產生的實際檔案名稱也跟 soname
類似,不過後面多了 minor 與 release 版本號碼,中間的 minor 號碼是用於標示新增加的介面,而最後面的 release 號碼則是用於程式內容的修正(介面不變的情況)。
如果程式使用到這個共享函式庫,則在執行時就會依據 soname
所指定的名稱來尋找函式庫的檔案,如果想要看共享函式庫的 soname
屬性,可以使用 objdump
指令:
objdump -p libsum.so.1.0.0 | grep SONAME
SONAME libsum.so.1
建立好共享函式庫之後,要建立一個不含版本號碼的 .so
連結檔,gcc
在連結時所需要的函式庫檔案是這一個:
ln -s libsum.so.1.0.0 libsum.so
另外再建立一個執行時要用的連結檔:
ln -s libsum.so.1.0.0 libsum.so.1
使用共享函式庫來編譯執行檔:
gcc main.c -L. -lsum -o main_dynamic
或是這樣編譯也可以:
gcc main.c libsum.so -o main_dynamic
編譯完成後,要執行時需要指定 LD_LIBRARY_PATH
:
LD_LIBRARY_PATH=. ./main_dynamic
2.6 + 4.2 = 6.8
這一個 main_dynamic
執行檔在執行時,會需要 libsum.so.1
這一個共享函式庫檔案:
LD_LIBRARY_PATH=. ldd main_dynamic
linux-vdso.so.1 => (0x00007ffc5f1f7000) libsum.so.1 => ./libsum.so.1 (0x00007efd0148d000) libc.so.6 => /lib64/libc.so.6 (0x00007efd010b3000) /lib64/ld-linux-x86-64.so.2 (0x00007efd01690000)
動態載入函式庫
動態載入函式庫(dynamically loaded library)就類似 Windows 的 dll 檔,是等到程式真正要用到時才會載入函式庫,其實作方式是透過 DL 函式庫配合一般的共享函式庫來處理的,以下是將之前製作的 libsum.so.1
共享函式庫改為動態載入的範例。
#include <stdlib.h> #include <stdio.h> #include <dlfcn.h> int main(int argc, char **argv) { void *handle; double (*sum)(double, double); char *error; // 動態開啟共享函式庫 handle = dlopen ("libsum.so.1", RTLD_LAZY); if (!handle) { fputs (dlerror(), stderr); exit(1); } // 取得 sum 函數的位址 sum = dlsym(handle, "sum"); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } // 使用 sum 函數 double a = 2.6, b = 4.2, c; c = sum(a, b); printf("%.1f + %.1f = %.1f\n", a, b, c); // 關閉共享函式庫 dlclose(handle); return 0; }
將這段程式碼儲存為 main_dl.c
之後,按照一般的方式進行編譯:
gcc main_dl.c -ldl -o main_dl
在使用上跟共享函式庫差不多,一樣要指定 LD_LIBRARY_PATH
:
LD_LIBRARY_PATH=. ./main_dl
2.6 + 4.2 = 6.8
表面上看起來跟一般的共享函式庫類似,不過這種動態載入函式庫方式與共享函式庫有很大的不同,共享函式庫是在程式一開始執行時就要載入(不管實際上有沒有使用到),而動態載入函式庫的做法則是可以在真正需要用到時才載入(如果沒用到就可以不需要載入)。
沒有留言:
張貼留言