静态链接在 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
:
$ 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 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
的目标文件:
$ 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
选项告诉链接器将 libmymath.a
中存在的目标文件与目标文件 mathDemo.o
链接> 创建最终的可执行文件。 -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
看起来不错!
何时使用静态链接
动态链接的可执行文件通常优于静态链接的可执行文件,因为动态链接使应用程序的组件保持模块化。如果库收到重要的安全更新,则可以轻松对其进行修补,因为它存在于使用它的应用程序之外。
当您使用静态链接时,库的代码会“隐藏”在您创建的可执行文件中,这意味着修补它的唯一方法是每次库获得更新时重新编译并重新发布新的可执行文件 - 而且您有更好的选择与你的时间有关的事情,相信我。
但是,如果库的代码与使用该库的可执行文件存在于同一代码库中,或者存在于预计不会接收更新的专用嵌入式设备中,则静态链接是一个合理的选择。