预处理、编译、链接都在做什么

最近先写一个简单的,之后再写复杂的。

在初学C语言时,我们往往被告知C语言的编译过程是预处理、编译和链接,那么这些过程都是什么呢?

下面以Linux下常用的gcc编译器举例说明我们平时见到的这些过程都是什么。

gcc原名GNU C Compiler,后改名 GNU Compiler Collection,下面的介绍也能让你明白为什么gcc换了名字。

预处理

我们先写一个入门C语言程序hello.c。这里puts的功能与printf类似,但是其只能输出纯字符串,不能像printf那样输出其他变量,使用puts代替printf是因为printf相关的优化较多且无法关闭,实际操作和理论一致性较差。

1
2
3
4
5
6
7
8
#include <stdio.h>

#define OK 0

int main() {
puts("Hello, World!\n");
return OK;
}

在C语言中,一些以#开头的行,被称为预处理指令,它们通常没有任何缩进,可以出现在文件中的任意一行,一些指令会被预处理器处理,最后生成新的.i的文本文件,这些.i文件会实际上参与下一步 - 编译。

预处理指令主要有以下功能

  • 处理包含指令。上面代码中相当于将 stdio.h内的所有内容粘贴到到#include所在的行并删除该行,当然如果stdio.h中也有#include指令那么它也会被处理,直到没有该指令。
  • 处理宏。例如将上面代码中的OK替换为0
  • 处理条件编译指令#if#ifdef
  • 更改编译器的行为(如字节对齐)

需要注意的是,预处理器并不会处理所有的预处理指令。有一些虽然定义上是预处理指令,但实际上会在编译阶段起作用,比如#pragma指令通常在编译期生效,在.i文件中你仍然可以看到这些指令。

汇编

通常C编译器并不直接从C代码生成机器指令,而是会有一个生成汇编代码的步骤,再通过汇编器将汇编代码编译为机器代码。这种方式方便了程序员使用内联汇编。由于C编译器生成的汇编代码有缩进格式,所以往往能见到使用多行内联汇编时会在\n后加\t(不加也一样)。该阶段生成的代码人类仍然可读。查看改代码可以发现头文件的大部分内容都已经消失,只剩下实际使用的puts这一个函数的名称了。

编译

汇编器会将刚才生成的汇编代码转换为机器可执行的代码。它会生成.o文件,此时该文件人类已经不可读,但其仍然和汇编文件基本对应,可以使用objdump工具查看其内容。

链接

链接是生成可执行程序的最后一步,它被用于处理符号引用。我们平时使用的printfputs均会在此处被处理。链接分为动态链接和静态链接,通常我们使用的都是动态链接方式。

动态链接需要分别在两个阶段进行:编译时与运行时(这里说的编译是指源代码到生成可执行文件的整个流程,通常编译指的都是这个流程)。编译时的链接用于指定某个符号在哪个动态库中,而运行时链接则为真正在内存中装载函数和变量。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2025 Ytyan

请我喝杯咖啡吧~