Linux 上静态链接的工作原理

了解如何使用静态库将多个 C 目标文件组合成单个可执行文件。
3 位读者喜欢这个。
Business woman on laptop sitting in front of window

图片由 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.csub.cmult.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);
}

现在使用 GCC 生成目标文件 add.osub.omult.odivi.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.olibmymath.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

看起来不错!

何时使用静态链接

动态链接的可执行文件通常优于静态链接的可执行文件,因为动态链接使应用程序的组件模块化。 如果库收到关键的安全更新,则可以轻松对其进行修补,因为它存在于使用它的应用程序之外。

当您使用静态链接时,库的代码会“隐藏”在您创建的可执行文件中,这意味着唯一可以修补它的方法是每次库获得更新时都重新编译并重新发布新的可执行文件,相信我,您有更好的事情要做。

但是,如果库的代码与使用它的可执行文件位于同一代码库中,或者位于预计不会收到更新的专用嵌入式设备中,则静态链接是一个合理的选择。

接下来阅读什么
标签
User profile image.
Jayashree Huttanagoudar 是 Red Hat India Pvt ltd 的高级软件工程师。 她与中间件 OpenJDK 团队合作。 她总是渴望学习新事物,这增加了她的工作。

1 条评论

不错,谢谢!

Creative Commons License本作品采用 Creative Commons Attribution-Share Alike 4.0 International License 授权。
© . All rights reserved.