了解如何用静态库将多个C对象文件合并成一个可执行文件。

图片来源:Mapbox Uncharted ERG,CC-BY 3.0 US
使用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
看起来不错!
什么时候使用静态链接
动态链接的可执行文件通常比静态链接的可执行文件更受欢迎,因为动态链接可以使应用程序的组件保持模块化。如果一个库收到一个关键的安全更新,它可以很容易地被修补,因为它存在于使用它的应用程序之外。
当你使用静态链接时,一个库的代码被 "隐藏 "在你创建的可执行文件中,这意味着修补它的唯一方法是在每次库得到更新时重新编译并重新发布一个新的可执行文件--而你有更好的事情要做,相信我。
然而,如果一个库的代码存在于与使用它的可执行文件相同的代码库中,或者存在于预计不会收到更新的专门的嵌入式设备中,静态链接是一个合理的选择。