编译基础
在学习了 C 编程的基本概念后,我们可以简要讨论编译过程。

像任何编程语言一样,C 本身对微处理器是完全不可理解的。C 的目的是为人类提供一种直观的方式,来给出可以轻松转化为机器码并且微处理器能理解的指令。编译器就是将我们可读的源代码转换成机器码的工具。

对于编程初学者来说,这似乎非常简单。一个初级的编译器可能会读取每个源文件,将所有内容转化为机器码,并输出可执行文件。这是可行的,但有两个严重的问题。首先,对于一个大型项目,计算机可能没有足够的内存一次性读取所有源代码。其次,如果你修改了一个源文件,你将不得不重新编译整个应用程序。

为了解决这些问题,编译器将工作分解为多个步骤。对于每个源文件(每个 .c 文件),编译器会读取该文件,读取它通过 #include 指令引用的文件,并将它们翻译成机器码。结果是一个“目标文件”(.o)。所有目标文件创建完成后,一个“链接器”程序会收集所有目标文件并生成实际的可执行程序。这样,如果你修改了一个源文件,只需要重新编译该文件,然后应用程序就需要重新链接。

在不详细讲解的情况下,简单了解编译过程的概述是有益的。

预处理器

预处理器提供了包括所谓头文件、宏扩展、条件编译和行控制的功能。通过将适当的预处理器指令插入到代码中,可以访问这些功能。在编译源代码之前,一个特殊的程序——预处理器,会扫描源代码中的标记或特殊字符串,并根据特定规则将其替换为其他字符串或代码。C 预处理器技术上并不属于 C 语言的一部分,而是由编译器软件提供的工具。

所有预处理器指令都以井号(#)开头。你可以在 "Hello world" 程序中看到一个预处理器指令。例如:

#include <stdio.h>

此指令使得 stdio 头文件被包含到程序中。其他指令如 #pragma 控制编译器设置和宏。预处理阶段的结果是一个文本字符串。你可以将预处理器看作是一个非交互式的文本编辑器,它会修改你的代码,为编译做准备。预处理指令的语法与 C 语言的语法无关,因此 C 预处理器也可以独立使用,用于处理其他类型的文本文件。

语法检查

这一阶段确保代码有效,并能够顺利地序列化为可执行程序。在大多数编译器中,如果程序有潜在问题(例如条件语句总是为真或为假等),你可能会收到消息或警告。

当程序中检测到错误时,编译器通常会报告文件名和阻止编译的行。

目标代码

编译器生成与源代码相对应的机器码,但此时代码本身不能执行,因为还需要链接才能执行。

在讲解了基础内容后,需要注意的是,编译是一个“单行道”。也就是说,将 C 源文件编译成机器码是容易的,但“反编译”(将机器码转回 C 源代码)则不容易。虽然 C 的反编译工具确实存在,但它们生成的代码很难理解,只能用于逆向工程。

链接

链接将分开的目标文件组合成一个完整的程序,通过集成库和代码,最终生成一个可执行程序或库。链接由链接器程序执行,链接器通常是编译器套件的一部分。

在此阶段常见的错误是缺失或重复的函数。

自动化

对于大型 C 项目,许多程序员选择自动化编译,既为了减少用户交互需求,也为了通过仅重新编译修改过的文件来加速过程。

大多数集成开发环境(IDE)都有某种项目管理功能,这使得自动化变得非常简单。然而,项目管理文件通常只能由使用相同集成开发环境的用户使用,因此任何想要修改项目的人都需要使用相同的 IDE。

在类似 UNIX 的系统中,makeMakefile 常用于完成相同的任务。make 是传统的、灵活的,并且是大多数 Unix 和 GNU 分发版的标准开发工具之一。

Makefile 已被 GNU Autotools 扩展,包含 Automake 和 Autoconf,用于使软件能够在多种类型的机器上进行编译、测试、翻译和配置。Automake 和 Autoconf 在各自的手册中有详细介绍。

Autotools 经常被认为比较复杂,因此出现了许多更简单的构建系统。现在,GNOME 项目的许多组件使用声明性更强的 Meson 构建系统,它虽然不如 Autotools 灵活,但更专注于简单地提供大多数构建系统常见的功能。其他用于 C 语言编写程序的流行构建系统包括 CMake 和 Waf。

一旦 GCC 安装完成,可以通过列出尚未编译的 C 源文件来调用它。例如,如果文件 main.c 中包含在 myfun.h 中描述的函数,并在 myfun_a.cmyfun_b.c 中实现,那么只需要写:

gcc main.c myfun_a.c myfun_b.c

myfun.h 已经在 main.c 中包含,但如果它在一个单独的头文件目录中,可以在 -I 开关后列出该目录。

在较大的程序中,Makefile 和 GNU make 程序可以将 C 文件编译为以 .o 后缀结尾的中间文件,这些文件可以通过 GCC 链接。

如何编译每个目标文件通常会在 Makefile 中描述,目标文件作为标签,以冒号结尾,后面跟着两个空格(制表符常常引起问题),再后面是其他依赖的文件列表,例如 .c 文件和在其他部分编译的 .o 文件,接下来的行是调用 GCC 所需的命令。

输入 man makeinfo make 通常会提供有关如何使用 make 和 GCC 所需的信息。

尽管 GCC 有很多选项开关,但常用的一个是 -g,它用于生成调试信息,以便 gdb 在逐步执行机器码程序时显示源代码。gdb 有一个 h 命令,显示它能做什么,并通常通过 gdb a.out 启动,如果 a.out 是通过 GCC 编译的匿名可执行机器码文件。

最后修改: 2025年01月12日 星期日 13:22