實例詳解 - 靜態鏈接在 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.cdivi.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);
}
現在生成目標文件add.o, sub.o, mult.o, divi.o,并使用 GCC:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? gcc -c add.c sub.c mult.c divi.c
該-c選項跳過鏈接步驟并僅創建目標文件。
創建一個名為 的靜態庫libmymath.a,然后刪除不再需要的目標文件。(請注意,使用比rm命令使用更安全的trash命令 。)
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? 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 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;
}
}
請注意,第一行是include按名稱引用您自己的libmymath庫的語句。
從mathDemo.c創建一個名為mathDemo.o的目標文件:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? gcc -I . -c mathDemo.c
該-I 選項告訴 GCC 搜索其后列出的頭文件。在這種情況下,您指定的是當前目錄,由一個點 ( .) 表示。
鏈接mathDemo.o以libmymath.a創建最終的可執行文件。有兩種方法可以向 GCC 表達這一點。
您可以指向文件:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? gcc -static -o mathDemo mathDemo.o libmymath.a
或者,您可以指定庫路徑以及庫名稱:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? gcc -static -o mathDemo -L . mathDemo.o -lmymath
在后一個示例中,該-lmymath選項告訴鏈接器將存在的目標文件libmymath.a與目標文件鏈接mathDemo.o以創建最終的可執行文件。該-L選項指示鏈接器在以下參數中查找庫(類似于您將使用的-I)。
分析結果
使用file命令確認它是靜態鏈接的:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? file mathDemo
mathDemo: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=c43a2cdedc68087433caf94b67ae079a02bc0bc9, for GNU/Linux 3.2.0, not stripped
使用ldd命令,可以看到可執行文件沒有動態鏈接:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? ldd ./mathDemo
不是動態可執行文件
您還可以檢查可執行文件mathDemo的大小:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? du -h ./mathDemo
984K ./mathDemo
運行命令查看它的工作情況:
linuxmi@linuxmi /home/linuxmi/www.linuxmi.com
? ./mathDemo
Enter two numbers
20
5
20 + 5 = 25
20 - 5 = 15
20 * 5 = 100
20 / 5 = 4
何時使用靜態鏈接
動態鏈接的可執行文件通常比靜態鏈接的可執行文件更受歡迎,因為動態鏈接使應用程序的組件保持模塊化。如果庫收到重要的安全更新,它可以很容易地修補,因為它存在于使用它的應用程序之外。
當您使用靜態鏈接時,庫的代碼會“隱藏”在您創建的可執行文件中,這意味著修補它的唯一方法是在每次庫獲得更新時重新編譯和重新發布新的可執行文件 - 你有更好的與你的時間有關的事情,相信我。
但是,如果庫的代碼存在于與使用它的可執行文件相同的代碼庫中,或者存在于預期不會接收更新的專用嵌入式設備中,則靜態鏈接是一個合理的選擇。