靜態
副檔名:.a (Linux)、.lib (Windows)編譯時期就把函式庫直接連結 (link) 到程式 (program) 中,而且是直接把函式庫中的目的碼直接貼到主程式中,因此大部分情況下,主程式會比使用動態函式庫的還大。注意 linux 的命名規則要是 lib 開頭 e.g. libdlist.a。
優點:一個執行檔就可以執行不需要依靠其他檔案。
缺點:若是函式庫內容有更動就需要全部重新編譯,通常耗時。另外還需要注意一些授權問題,因為別人的程式碼被你編譯進你的程式,所以如果有註明要符合的規定要特別小心。最後就是執行檔體積較大。
動態
副檔名:.so (Linux)、.dll (Windows)、.dylib (OS X)在執行時期才載入 (load) 到記憶體中,主程式中只有記錄簡單的參考位置,執行時期再呼叫使用。注意 linux 的命名規則要是 lib 開頭 e.g. libdlist.so。
優點:主程式體積小。可以達到熱抽換,也就是更換新的動態函式庫時,主程式大部分是不需要重新編譯。
缺點:相依的檔案若不存在就無法執行。檔案數量多,就像 windows 有些程式有一堆 .dll 檔一樣
補充:
.a (archive)、.so (shared object)、.dll (dynamic-link library)
參考資料:
Difference between static and shared libraries?
.o .a .so .dll的区别
實際操作
實驗過後發現 win10 下的 Cygwin 可以用 .a、.lib、.dll,但是不能用 .soubuntu 16.04 下當然是可以用 .a、.so
<注意>實驗中所使用的 gcc 在 Cygwin 下為 Cygwin 內部的 gcc,若要使 windows 可以直接執行,應改用 x86_64-w64-mingw32-gcc
實驗程式:
adder.h#ifndef _ADDER_H #define _ADDER_H int add(int a, int b); #endifadder.c
#include "adder.h" int add(int a, int b) { return a + b; }main.c
#include <stdio.h> #include "adder.h" int main() { int x = 1; int y = 2; int z = add(x, y); printf("%d\n", z); }
編譯與函式庫連結指令
0. 不使用函式庫$ gcc -c adder.c $ gcc -c main.c $ gcc -o main adder.o main.o1. Cygwin - static (.a)
$ gcc -c adder.c $ ar rcs libadder.a adder.o $ gcc -c main.c $ gcc -o main main.o -static -L. -ladder2. Cygwin - static (.lib)
$ gcc -c adder.c $ ar rcs adder.lib adder.o $ gcc -c main.c $ gcc -o main main.o -static -L. -ladder3. Cygwin - shared (.dll)
$ gcc -c main.c $ gcc -c -fPIC adder.c $ gcc -o adder.dll -shared adder.o $ gcc -o main main.o -L. -ladder -Wl,-rpath=.4. Ubuntu - static (.a)
$ gcc -c adder.c $ ar rcs libadder.a adder.o $ gcc -c main.c $ gcc -o main main.o -static -L. -ladder5. Ubuntu - shared (.so)
$ gcc -c main.c $ gcc -c -fPIC adder.c $ gcc -o libadder.so -shared adder.o $ gcc -o main main.o -L. -ladder -Wl,-rpath=.
執行
依照各方式執行後,都會產生一個 main 的執行檔,執行後理論上都會輸出 3$ ./main另外,關於動態函式庫部分可以試試看把 .so、.dll 刪除後再執行一次
靜態的也可以試試同樣操作
補充:
指令講解
$ gcc -c xxx.c:把 xxx.h、xxx.c 編譯成尚未連結的目的碼 xxx.o,這裡的"編譯"比較廣,實際是經由預處理、編譯、組譯。
$ gcc -o ccc aaa.o bbb.o:把 aaa.o、bbb.o 連結到一個可執行檔 ccc。
$ ar rcs libadder.a adder.o:ar (archiver 用於產生 .a 檔) 把 adder.o 的內容放到靜態庫 libadder.a 中,如下圖 (上半為 libadder.a,下半為 adder.o,可以看到重複部分)。至於 rcs 參數,可參考 What does the “rcs” option in ar do?
$ gcc -o main main.o -static -L. -ladder:-static 選項標記使用靜態庫。-L 是加增函式庫搜尋路徑,-L. 表示把當前路徑 (.) 加入搜尋路徑中,若不使用則只會搜尋預設路徑。-l 表示使用的函式庫名稱,這裡可以看到很特別的地方,函式庫檔案為 libadder.a,-ladder 不需要包含 lib。
$ gcc -c -fPIC xxx.c:多了 -fPIC (Position Independent Code) 選項,主要是不使用絕對位址,這在動態庫是很重要的步驟,因為動態就不能確定每次使用的位址。可參考 GCC -fPIC option
$ gcc -o adder.dll -shared adder.o、\$ gcc -o libadder.so -shared adder.o:產生動態庫時與靜態不同,動態是使用 gcc 和 -shared 選項完成。
$ gcc -o main main.o -L. -ladder -Wl,-rpath=.:-Wl,-rpath=. 表示在連結時期就先告知在執行時期要搜尋當前路徑 (.)。
參考資料:
Creating a shared and static library with the gnu compiler (gcc)
What is the difference between `-fpic` and `-fPIC` gcc parameters?
3.16 Options for Code Generation Conventions
[Linux] 在 gcc 中使用 rpath/rpath-link 指定 shared library 搜尋路徑
rpath添加依赖库搜索路径
結論
可以發現 Cygwin - static (.a)、Ubuntu - static (.a) 兩個版本指令相同把動態函式庫刪除後就無法執行,但是刪除靜態函式庫會發現還是可以執行,原因是因為靜態已經把內容物都複製到 main 執行檔中了,刪除並不影響執行,而動態會在執行時把相關的函式庫載入,若找不到的話當然就報錯。
檔案大小看不太出差異,主因是因為這個範例的函式庫太少東西了 @@
參考資料:
Shared libraries with GCC on Linux
[C++] 如何用 gcc 編出 library 讓其他人使用-- DLL
Program Library HOWTO
Hello World: C, Assembly, Object File and Executable
沒有留言:
張貼留言