Linux 上靜態(tài)鏈接庫(kù)工作原理
學(xué)習(xí)如何用靜態(tài)鏈接庫(kù)將多個(gè) C 目標(biāo)文件結(jié)合到一個(gè)單個(gè)的可執(zhí)行文件之中。
使用 C 編寫的應(yīng)用程序時(shí),通常有多個(gè)源碼文件,但最終你需要編譯成單個(gè)的可執(zhí)行文件。

你可以通過兩種方式來完成這項(xiàng)工作:通過創(chuàng)建一個(gè) 靜態(tài)static 庫(kù) 或 一個(gè) 動(dòng)態(tài)dynamic 庫(kù)(也被稱為 共享shared 庫(kù))。從創(chuàng)建和鏈接的方式來看,它們是兩種不同類型的庫(kù)。選擇使用哪種方式取決于你的的具體場(chǎng)景。
在 上一篇文章 中,我演示了如何創(chuàng)建一個(gè)動(dòng)態(tài)鏈接的可執(zhí)行文件,這是一種更通用的方法。在這篇文章中,我將說明如何創(chuàng)建一個(gè)靜態(tài)鏈接的可執(zhí)行文件。
使用靜態(tài)庫(kù)鏈接器
鏈接器linker是一個(gè)命令,它將一個(gè)程序的多個(gè)部分結(jié)合在一起,并為它們重新組織內(nèi)存分配。
鏈接器的功能包括:
- 整合一個(gè)程序的所有的部分
- 計(jì)算出一個(gè)新的內(nèi)存組織結(jié)構(gòu),以便所有的部分組合在一起
- 恢復(fù)內(nèi)存地址,以便程序可以在新的內(nèi)存組織結(jié)構(gòu)下運(yùn)行
- 解析符號(hào)引用
鏈接器通過這些功能,創(chuàng)建了一個(gè)名稱為可執(zhí)行文件的一個(gè)可運(yùn)行程序。
靜態(tài)庫(kù)是通過復(fù)制一個(gè)程序中的所有依賴庫(kù)模塊到最終的可執(zhí)行鏡像來創(chuàng)建的。鏈接器將鏈接靜態(tài)庫(kù)作為編譯過程的最后一步。可執(zhí)行文件是通過解析外部引用、將庫(kù)例程與程序代碼結(jié)合在一起來創(chuàng)建的。
創(chuàng)建目標(biāo)文件
這里是一個(gè)靜態(tài)庫(kù)的示例以及其鏈接過程。首先,創(chuàng)建帶有這些函數(shù)識(shí)別標(biāo)志的頭文件 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);
使用這些函數(shù)定義來創(chuàng)建 add.c 、sub.c 、mult.c 和 divi.c 文件。我將把所有的代碼都放置到一個(gè)代碼塊中,請(qǐng)將其分為四個(gè)文件,如注釋所示:
// 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);
}
現(xiàn)在,使用 GCC 來生成目標(biāo)文件 add.o 、sub.o 、mult.o 和 divi.o:
(LCTT 校注:關(guān)于“目標(biāo)文件object file”,有時(shí)候也被稱作“對(duì)象文件”,對(duì)此,存在一些譯法混亂情形,稱之為“目標(biāo)文件”的譯法比較流行,本文采用此譯法。)
$ gcc -c add.c sub.c mult.c divi.c
-c 選項(xiàng)跳過鏈接步驟,而只創(chuàng)建目標(biāo)文件。
創(chuàng)建一個(gè)名稱為 libmymath.a 的靜態(tài)庫(kù),接下來,移除目標(biāo)文件,因?yàn)樗鼈儾辉俦恍枰#ㄗ⒁猓褂靡粋€(gè) trash 命令比使用一個(gè) 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
現(xiàn)在,你已經(jīng)創(chuàng)建了一個(gè)名稱為 libmymath 的簡(jiǎn)單數(shù)學(xué)示例庫(kù),你可以在 C 代碼中使用它。當(dāng)然,也有非常復(fù)雜的 C 庫(kù),這就是他們這些開發(fā)者來生成最終產(chǎn)品的工藝流程,你和我可以安裝這些庫(kù)并在 C 代碼中使用。
接下來,在一些自定義代碼中使用你的數(shù)學(xué)庫(kù),然后鏈接它。
創(chuàng)建一個(gè)靜態(tài)鏈接的應(yīng)用程序
假設(shè)你已經(jīng)為數(shù)學(xué)運(yùn)算編寫了一個(gè)命令。創(chuàng)建一個(gè)名稱為 mathDemo.c 的文件,并將這些代碼復(fù)制粘貼至其中:
#include <mymath.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x, y;
printf("Enter two numbers\n");
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 division\n");
exit(0);
}else{
printf("\n%d / %d = %d\n", x, y, divi(x, y));
return 0;
}
}
注意:第一行是一個(gè) include 語句,通過名稱來引用你自己的 libmymath 庫(kù)。
針對(duì) mathDemo.c 創(chuàng)建一個(gè)名稱為 mathDemo.o 的對(duì)象文件:
$ gcc -I . -c mathDemo.c
-I 選項(xiàng)告訴 GCC 搜索在其后列出的頭文件。在這個(gè)實(shí)例中,你通過單個(gè)點(diǎn)(.)來指定當(dāng)前目錄。
鏈接 mathDemo.o 和 libmymath.a 來生成最終的可執(zhí)行文件。這里有兩種方法來向 GCC 告知這一點(diǎn)。
你可以指向文件:
$ gcc -static -o mathDemo mathDemo.o libmymath.a
或者,你可以具體指定庫(kù)的路徑及名稱:
$ gcc -static -o mathDemo -L . mathDemo.o -lmymath
在后面的那個(gè)示例中,-lmymath 選項(xiàng)告訴鏈接器來鏈接對(duì)象文件 mathDemo.o 和對(duì)象文件 libmymath.a 來生成最終的可執(zhí)行文件。-L 選項(xiàng)指示鏈接器在下面的參數(shù)中查找?guī)欤愃朴谀闶褂?nbsp;-I 所做的工作)。
分析結(jié)果
使用 file 命令來驗(yàn)證它是靜態(tài)鏈接的:
$ file mathDemo
mathDemo: ELF 64-bit LSB executable, x86-64...
statically linked, with debug_info, not stripped
使用 ldd 命令,你將會(huì)看到該可執(zhí)行文件不是動(dòng)態(tài)鏈接的:
$ ldd ./mathDemo
not a dynamic executable
你也可以查看 mathDemo 可執(zhí)行文件的大小:
$ du -h ./mathDemo
932K ./mathDemo
在我 前一篇文章 的示例中,動(dòng)態(tài)鏈接的可執(zhí)行文件只占有 24K 大小。
運(yùn)行該命令來看看它的工作內(nèi)容:
$ ./mathDemo
Enter two numbers
10
5
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
看起來令人滿意!
何時(shí)使用靜態(tài)鏈接
動(dòng)態(tài)鏈接可執(zhí)行文件通常優(yōu)于靜態(tài)鏈接可執(zhí)行文件,因?yàn)閯?dòng)態(tài)鏈接會(huì)保持應(yīng)用程序的組件模塊化。假如一個(gè)庫(kù)接收到一次關(guān)鍵安全更新,那么它可以很容易地修補(bǔ),因?yàn)樗嬖谟趹?yīng)用程序的外部。
當(dāng)你使用靜態(tài)鏈接時(shí),庫(kù)的代碼會(huì)“隱藏”在你創(chuàng)建的可執(zhí)行文件之中,意味著在庫(kù)每次更新時(shí)(相信我,你會(huì)有更好的東西),僅有的一種修補(bǔ)方法是重新編譯和發(fā)布一個(gè)新的可執(zhí)行文件。
不過,如果一個(gè)庫(kù)的代碼,要么存在于它正在使用的具有相同代碼的可執(zhí)行文件中,要么存在于不會(huì)接收到任何更新的專用嵌入式設(shè)備中,那么靜態(tài)連接將是一種可接受的選項(xiàng)。

























