學習目標
- 理解靜態連結 (Static Linking) 的概念
- 理解動態連結 (Dynamic Linking) 的概念
- 掌握兩者的差異和優缺點
- 了解動態連結的運作方式
1. 連結 (Linking) 的基本概念
1.1 為什麼需要連結?
當我們編寫程式時,通常會: - 使用標準函式庫(如 printf, malloc) - 將程式分成多個原始檔 - 使用其他開發者提供的函式庫
連結 (Linking) 的過程就是將這些分散的程式碼組合在一起,形成一個可執行的程式。
1.2 編譯流程
原始碼 (.c)
↓ [編譯 Compilation]
目的檔 (.o)
↓ [連結 Linking]
可執行檔 (.exe 或無副檔名)
2. 靜態連結 (Static Linking)
2.1 定義
靜態連結是在編譯時期(或連結時期)將所有需要的函式庫程式碼直接複製到最終的可執行檔中。
2.2 運作方式
- 編譯階段: 將原始碼編譯成目的檔 (.o)
- 連結階段: 連結器 (Linker) 將目的檔和靜態函式庫 (.a 或 .lib) 合併
- 結果: 產生一個完全獨立的可執行檔,包含所有需要的程式碼
2.3 範例
假設我們有一個程式 main.c 使用了標準函式庫的 printf:
// main.c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
靜態連結過程:
main.c → [編譯] → main.o
+
stdio.h 的實作 (靜態函式庫)
↓
[靜態連結]
↓
可執行檔 (包含 printf 的完整程式碼)
結果: 可執行檔包含了 printf 的完整實作程式碼。
2.4 優點
- 獨立性: 可執行檔完全獨立,不需要額外的函式庫檔案
- 效能: 執行時不需要載入函式庫,啟動速度快
- 相容性: 不依賴系統的函式庫版本,避免版本衝突
- 部署簡單: 只需要一個可執行檔
2.5 缺點
- 檔案大小: 可執行檔很大,因為包含所有函式庫程式碼
- 記憶體浪費: 多個程式執行時,相同的函式庫程式碼會在記憶體中重複存在
- 更新困難: 如果函式庫有更新,需要重新編譯和連結所有使用該函式庫的程式
- 編譯時間: 連結過程可能較慢
2.6 實際例子
Windows:
- 靜態函式庫: .lib 檔案
- 連結後: 可執行檔 .exe 包含所有程式碼
Linux/Unix:
- 靜態函式庫: .a 檔案(Archive)
- 連結後: 可執行檔包含所有程式碼
3. 動態連結 (Dynamic Linking)
3.1 定義
動態連結是在執行時期才載入和連結函式庫。可執行檔中只包含對函式庫的參考 (Reference),而不是完整的程式碼。
3.2 運作方式
- 編譯階段: 將原始碼編譯成目的檔
- 連結階段: 連結器只記錄需要哪些動態函式庫,不複製程式碼
- 執行階段: - 作業系統載入可執行檔 - 動態連結器 (Dynamic Linker) 載入所需的動態函式庫 - 解析函式位址 - 程式開始執行
3.3 範例
同樣的 main.c 程式:
動態連結過程:
編譯時期:
main.c → [編譯] → main.o
↓
[動態連結]
↓
可執行檔 (只包含對 printf 的參考)
執行時期:
可執行檔載入
↓
動態連結器載入 libc.so (包含 printf 的實作)
↓
解析 printf 的位址
↓
程式執行
結果: 可執行檔很小,但執行時需要系統中有對應的動態函式庫。
3.4 動態函式庫檔案
Windows:
- 動態函式庫: .dll (Dynamic Link Library)
- 例如: kernel32.dll, user32.dll
Linux/Unix:
- 動態函式庫: .so (Shared Object)
- 例如: libc.so, libm.so
macOS:
- 動態函式庫: .dylib (Dynamic Library)
3.5 優點
- 檔案大小: 可執行檔較小,因為不包含函式庫程式碼
- 記憶體效率: 多個程式可以共享同一個動態函式庫的記憶體空間
- 更新容易: 更新函式庫檔案後,所有使用該函式庫的程式自動使用新版本
- 模組化: 函式庫可以獨立開發和維護
3.6 缺點
- 依賴性: 可執行檔依賴系統中的動態函式庫,如果缺少會無法執行
- 版本衝突: 不同程式可能需要不同版本的函式庫,可能造成衝突
- 啟動時間: 執行時需要載入和連結函式庫,啟動可能稍慢
- 部署複雜: 需要確保目標系統有正確版本的函式庫
3.7 動態連結的兩種方式
載入時連結 (Load-time Linking)
- 程式啟動時,動態連結器載入所有需要的函式庫
- 解析所有函式的位址
- 然後才開始執行程式
執行時連結 (Run-time Linking)
- 程式執行過程中,需要時才載入函式庫
- 使用
dlopen()(Linux) 或LoadLibrary()(Windows) 等函式 - 更靈活,但需要程式設計師手動管理
4. 靜態連結 vs 動態連結比較
| 特性 | 靜態連結 | 動態連結 |
|---|---|---|
| 連結時機 | 編譯時期 | 執行時期 |
| 函式庫程式碼位置 | 在可執行檔內 | 在獨立的函式庫檔案中 |
| 可執行檔大小 | 較大 | 較小 |
| 記憶體使用 | 多個程式重複載入 | 多個程式共享 |
| 部署 | 簡單(只需一個檔案) | 需要函式庫檔案 |
| 更新 | 需要重新編譯 | 只需更新函式庫檔案 |
| 啟動速度 | 較快 | 稍慢(需載入函式庫) |
| 依賴性 | 無依賴 | 依賴系統函式庫 |
5. 實際應用場景
5.1 適合使用靜態連結的情況
- 獨立應用程式: 需要完全獨立,不依賴系統環境
- 嵌入式系統: 資源有限,需要確定性
- 單一執行檔部署: 希望部署簡單
- 效能關鍵: 需要最快的啟動速度
5.2 適合使用動態連結的情況
- 系統函式庫: 作業系統提供的標準函式庫
- 大型應用程式: 多個程式共享函式庫
- 需要更新: 函式庫需要經常更新
- 記憶體受限: 需要節省記憶體
6. 混合方式
實際上,許多程式會混合使用兩種方式: - 核心功能: 使用靜態連結,確保獨立性 - 標準函式庫: 使用動態連結,節省空間和記憶體
7. 練習題
練習題 1
請說明靜態連結和動態連結的主要差異。
參考答案: - 靜態連結: 編譯時期將函式庫程式碼複製到可執行檔中,可執行檔獨立但較大 - 動態連結: 執行時期才載入函式庫,可執行檔較小但依賴系統函式庫
練習題 2
為什麼動態連結可以節省記憶體?
參考答案: 因為多個程式可以共享同一個動態函式庫的記憶體空間,而不像靜態連結那樣每個程式都在記憶體中有自己的函式庫程式碼副本。
練習題 3
在什麼情況下應該使用靜態連結?
參考答案: 當需要完全獨立的應用程式、在嵌入式系統中、需要最快的啟動速度、或希望部署簡單(只需一個檔案)時,應該使用靜態連結。
8. 考試重點提醒
- 連結時機: 靜態連結在編譯時期,動態連結在執行時期
- 程式碼位置: 靜態連結的程式碼在可執行檔內,動態連結在獨立的函式庫檔案中
- 優缺點: 要能清楚比較兩者的優缺點
- 記憶體共享: 理解為什麼動態連結可以節省記憶體
- 檔案格式: 了解不同系統的函式庫檔案格式(.a, .so, .dll)