107.08.25 C 語言 static 和 shared 函式庫

函式庫 (Library) 主要分成二種,靜態函式庫 (static library)、動態函式庫 (shared library)

靜態

副檔名:.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,但是不能用 .so
ubuntu 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);

#endif
adder.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.o
1. Cygwin - static (.a)
$ gcc -c adder.c
$ ar rcs libadder.a adder.o
$ gcc -c main.c
$ gcc -o main main.o -static -L. -ladder
2. Cygwin - static (.lib)
$ gcc -c adder.c
$ ar rcs adder.lib adder.o
$ gcc -c main.c
$ gcc -o main main.o -static -L. -ladder
3. 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. -ladder
5. 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

沒有留言:

張貼留言

^ Top