0%

C++ 编译与链接

1. 编译系统

从源码文件到可执行程序是如何实现的呢?下面写个简单的程序来进行说明。
以下是一个 hello.c 程序:

1
2
3
4
5
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}

在Linux系统上,由编译器把源文件转换为可执行文件的命令如下:

gcc hello.c -o hello

这是gcc编译器一步到位将源文件编译成了可执行文件,其中省略了的几个步骤,由gcc编译器自动执行。我们拆开来看,整个编译分为四个步骤:

1-> gcc -E hello.c -o hello.i
2-> gcc -S hello.i -o hello.s
3-> gcc -c hello.s -o hello.o
4-> gcc hello.o -o hello

步骤1:预处理阶段,处理以 # 开头的预处理命令;
步骤2:编译阶段,翻译成汇编文件;
步骤3:汇编阶段,将汇编文件翻译成可重定位目标文件(.o文件);
步骤4:链接阶段,将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。
compilie&link
以上就是编译链接成可执行文件的过程,下面介绍两种链接方式:静态链接动态链接

2. 静态链接

2.1 为什么需要静态链接

在实际开发中,我们不可能把所有的代码都写在一个源文件中,所以会出现很多源文件。这些源文件并非是独立的,而是有一定的依赖关系,如一个源文件需要调用另一个源文件中的函数,但是每个源文件都是独立编译的,即每个 .c 将会编译成对应的 .o 文件,那么就需要将这些目标文件进行链接,从而生成一个可执行文件,这个过程就是静态链接。

2.2 静态链接的过程

在Linux中,静态链接库以 .a 为文件后缀,如 libc.a 为 c语言的静态链接库。
在示例程序中,调用了 stdio.h 中的 printf 函数,在 libc.a 中找到 printf.o 和它的依赖目标文件,然后将这些依赖文件和我们的 hello.o 文件链接打包成一个可执行文件,过程如下图:
static

2.3 静态链接的优缺点

优点:

  1. 因为可执行程序中已经具备了执行程序所需要的所有东西,所以程序加载速度快,执行速度也快。
  2. 只需要保证开发者计算机上有正确的 .a 文件,在发布可执行程序时,不需要考虑发布机器上是否存在 .a 文件。

缺点:

  1. 使用静态链接生成的可执行文件体积较大,包含了相同的公共代码,造成空间浪费。
  2. 更新困难,如果静态库文件更新,需要重新编译链接整个可执行文件,再行发布。

3. 动态链接

为了解决静态链接的缺点,就需要使用到动态链接。
在Linux中,动态链接库以 .so 为文件后缀。
动态链接的思想就是将程序按照模块拆分为各个相对独立的部分,在程序运行时才将它们链接在一起形成完整的程序。
mem
优点:

  1. 更加节省内存并减少页面交换。
  2. 更新方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。
  3. 可是实现进程之间的资源共享。

缺点:

  1. 当某个动态库更新后,如果依赖该动态库的程序与更新后的动态库不兼容,则该程序将无法正常执行。
  2. 因为是运行时加载,所以相对于静态链接,性能会有所下降。