Linux 上靜態鏈接庫工作原理
學習如何用靜態鏈接庫將多個 C 目標文件結合到一個單個的可執行文件之中。
使用 C 編寫的應用程序時,通常有多個源碼文件,但最終你需要編譯成單個的可執行文件。
你可以通過兩種方式來完成這項工作:通過創建一個 靜態 庫 或 一個 動態 庫(也被稱為 共享 庫)。從創建和鏈接的方式來看,它們是兩種不同類型的庫。選擇使用哪種方式取決於你的的具體場景。
在 上一篇文章 中,我演示了如何創建一個動態鏈接的可執行文件,這是一種更通用的方法。在這篇文章中,我將說明如何創建一個靜態鏈接的可執行文件。
使用靜態庫鏈接器
鏈接器 是一個命令,它將一個程序的多個部分結合在一起,並為它們重新組織內存分配。
鏈接器的功能包括:
- 整合一個程序的所有的部分
- 計算出一個新的內存組織結構,以便所有的部分組合在一起
- 恢復內存地址,以便程序可以在新的內存組織結構下運行
- 解析符號引用
鏈接器通過這些功能,創建了一個名稱為可執行文件的一個可運行程序。
靜態庫是通過複製一個程序中的所有依賴庫模塊到最終的可執行鏡像來創建的。鏈接器將鏈接靜態庫作為編譯過程的最後一步。可執行文件是通過解析外部引用、將庫常式與程序代碼結合在一起來創建的。
創建目標文件
這裡是一個靜態庫的示例以及其鏈接過程。首先,創建帶有這些函數識別標誌的頭文件 mymath.h
:
int add(int a, int b);
int sub(int a, int b);
int mult(int a, int b);
int divi(int a, int b);
使用這些函數定義來創建 add.c
、sub.c
、mult.c
和 divi.c
文件。我將把所有的代碼都放置到一個代碼塊中,請將其分為四個文件,如注釋所示:
// add.c
int add(int a, int b){
return (a+b);
}
//sub.c
int sub(int a, int b){
return (a-b);
}
//mult.c
int mult(int a, int b){
return (a*b);
}
//divi.c
int divi(int a, int b){
return (a/b);
}
現在,使用 GCC 來生成目標文件 add.o
、sub.o
、mult.o
和 divi.o
:
(LCTT 校註:關於「 目標文件 」,有時候也被稱作「對象文件」,對此,存在一些譯法混亂情形,稱之為「目標文件」的譯法比較流行,本文採用此譯法。)
$ gcc -c add.c sub.c mult.c divi.c
-c
選項跳過鏈接步驟,而只創建目標文件。
創建一個名稱為 libmymath.a
的靜態庫,接下來,移除目標文件,因為它們不再被需要。(注意,使用一個 trash
命令比使用一個 rm
命令更安全。)
$ ar rs libmymath.a add.o sub.o mult.o divi.o
$ trash *.o
$ ls
add.c divi.c libmymath.a mult.c mymath.h sub.c
現在,你已經創建了一個名稱為 libmymath
的簡單數學示例庫,你可以在 C 代碼中使用它。當然,也有非常複雜的 C 庫,這就是他們這些開發者來生成最終產品的工藝流程,你和我可以安裝這些庫並在 C 代碼中使用。
接下來,在一些自定義代碼中使用你的數學庫,然後鏈接它。
創建一個靜態鏈接的應用程序
假設你已經為數學運算編寫了一個命令。創建一個名稱為 mathDemo.c
的文件,並將這些代碼複製粘貼至其中:
#include <mymath.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x, y;
printf("Enter two numbersn");
scanf("%d%d",&x,&y);
printf("n%d + %d = %d", x, y, add(x, y));
printf("n%d - %d = %d", x, y, sub(x, y));
printf("n%d * %d = %d", x, y, mult(x, y));
if(y==0){
printf("nDenominator is zero so can't perform divisionn");
exit(0);
}else{
printf("n%d / %d = %dn", x, y, divi(x, y));
return 0;
}
}
注意:第一行是一個 include
語句,通過名稱來引用你自己的 libmymath
庫。
針對 mathDemo.c
創建一個名稱為 mathDemo.o
的對象文件:
$ gcc -I . -c mathDemo.c
-I
選項告訴 GCC 搜索在其後列出的頭文件。在這個實例中,你通過單個點(.
)來指定當前目錄。
鏈接 mathDemo.o
和 libmymath.a
來生成最終的可執行文件。這裡有兩種方法來向 GCC 告知這一點。
你可以指向文件:
$ gcc -static -o mathDemo mathDemo.o libmymath.a
或者,你可以具體指定庫的路徑及名稱:
$ gcc -static -o mathDemo -L . mathDemo.o -lmymath
在後面的那個示例中,-lmymath
選項告訴鏈接器來鏈接對象文件 mathDemo.o
和對象文件 libmymath.a
來生成最終的可執行文件。-L
選項指示鏈接器在下面的參數中查找庫(類似於你使用 -I
所做的工作)。
分析結果
使用 file
命令來驗證它是靜態鏈接的:
$ file mathDemo
mathDemo: ELF 64-bit LSB executable, x86-64...
statically linked, with debug_info, not stripped
使用 ldd
命令,你將會看到該可執行文件不是動態鏈接的:
$ ldd ./mathDemo
not a dynamic executable
你也可以查看 mathDemo
可執行文件的大小:
$ du -h ./mathDemo
932K ./mathDemo
在我 前一篇文章 的示例中,動態鏈接的可執行文件只佔有 24K 大小。
運行該命令來看看它的工作內容:
$ ./mathDemo
Enter two numbers
10
5
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
看起來令人滿意!
何時使用靜態鏈接
動態鏈接可執行文件通常優於靜態鏈接可執行文件,因為動態鏈接會保持應用程序的組件模塊化。假如一個庫接收到一次關鍵安全更新,那麼它可以很容易地修補,因為它存在於應用程序的外部。
當你使用靜態鏈接時,庫的代碼會「隱藏」在你創建的可執行文件之中,意味著在庫每次更新時(相信我,你會有更好的東西),僅有的一種修補方法是重新編譯和發布一個新的可執行文件。
不過,如果一個庫的代碼,要麼存在於它正在使用的具有相同代碼的可執行文件中,要麼存在於不會接收到任何更新的專用嵌入式設備中,那麼靜態連接將是一種可接受的選項。
via: https://opensource.com/article/22/6/static-linking-linux
作者:Jayashree Huttanagoudar 選題:lkxed 譯者:robsean 校對:turbokernel
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive