# 第一章:基本介绍 ## 1.1 回顾 C 语言的编译过程 * C 语言的编译过程,如下所示: ![](./assets/1.png) * 过程 ① :编写(编辑)源代码,即:编写 C 语言源程序代码,并以文件的形式存储在磁盘中。 > [!NOTE] > > 源程序需要以 `.c` 作为扩展名。 * 过程 ② :编译,即:将 C 语言源程序转换为`目标程序(或目标文件)`。如果程序没有错误,没有任何提示,就会生成一个扩展名为 `.obj`或 `.o` 的二进制文件。C 语言中的每条可执行语句经过编译之后,最终都会转换为二进制的机器指令。 > [!NOTE] > > - ① 其实,`编译阶段`包含了`预处理`、`编译`和`汇编`。 > - ② `预处理`是编译过程的第一个阶段。在这个阶段,预处理器处理源代码中的指令(例如:`#include`、`#define`等),主要任务包括: > - 头文件包含:将头文件的内容插入到源文件中。例如:`#include `会被替换为`stdio.h`文件的内容。 > - 宏展开:替换宏定义。例如:`#define PI 3.14`会将代码中的`PI`替换为`3.14`。 > - 条件编译:根据条件指令(如:`#ifdef`、`#ifndef`)有选择地编译代码。 > - 删除代码中的注释,但是不会进行语法检查。 > - 预处理完成后,生成一个扩展名为`.i`的中间文件。 > - ③ `编译`是将预处理后的源代码转换为汇编代码的过程。在这个阶段,编译器会检查代码的语法和语义,将其转换为目标机器的汇编语言,生成一个扩展名为`.s`的汇编文件。 > - ④ `汇编`是将汇编代码转换为机器代码(也称为目标代码或目标文件)的过程。在这个阶段,汇编器将汇编指令转换为二进制机器指令,生成一个扩展名为`.o`或 `.obj`的目标文件。 * 过程 ③ :链接(连接),即:将编译形成的目标文件 `*.obj` 或 `*.o`和库函数以及其他目录文件`链接`,形成一个统一的二进制文件 `*.exe`。 > [!NOTE] > > - 为什么需要链接库文件? > - 因为我们的 C 程序会使用 C 程序库中的内容,如:`` 中的 `printf()` 函数,这些函数不是程序员自己写的,而是 C 程序库中提供的,因此需要链接。其实,在链接过程中,还会加入启动代码,这个启动代码(和系统相关,Linux 下主要有 crt0.c、crti.c 等,它们设置堆栈后,再调用 main() 函数)负责初始化程序运行时的环境。 - 过程 ④ :执行,即:有了可执行的 `*.exe`文件,我们就可以在控制台上执行运行此 `*.exe` 文件。 > [!NOTE] > > 如果`修改`了源代码,还需要重新`编译`、`链接`,并生成新的 `*.exe`文件,再执行,方能生效。 ## 1.2 和其他编程语言的对比 * 在编译和链接之前,C 语言需要对源文件进行一些文本方面的操作,如:删除代码中的注释,但是不会进行语法检查、头文件包含、宏展开、条件编译等,这个过程叫做预处理,由`预处理程序(预处理器)`完成。 * 较之其他的编程语言,如:Java 、C# 等,C/C++ 语言更依赖预处理器,所以在阅读或开发 C/C++ 程序的过程中,可能会接触到大量的预处理指令,如:`#include`、`#define` 等。 ## 1.3 预处理指令 * 预处理过程中会执行预处理指令,预处理指令以 `#`开头,如:`#include` 等,用于指导预处理器执行不同的任务。 * 预处理器有如下的特点: * ① 预处理指令应该放在代码的开头部分。 * ② 预处理指令都以 `#`开头,指令前面可以有空白字符(比如空格或制表符),`#`和指令的其余部分之间也可以有空格,但是为了兼容老的编译器,一般不留空格。 ```c // 推荐写法 #include ``` ```c // 不推荐写法 #include # include ``` * ③ 预处理指令都是一行的,除非在行尾使用反斜杠,将其折行,但强烈不建议这么干。 ```c #include ``` * ④ 预处理指令不需要分号作为结束符,指令结束是通过换行符来识别的。 ```c #include ; // 这里有分号会报错 ``` ```c #define PI 3.14; // 分号会成为 PI 的值的一部分 ``` * ⑤ 预处理指令通常不能写在函数内部,有些编译器的扩展允许将预处理指令写在函数里,但强烈不建议这么干。 ```c int main () { // 一般不允许写在这里 #include // [!code warning] return 0; } ``` ## 1.4 为什么 C 语言需要预处理? * C 语言并没有一个官方机构,也不属于哪个公司,它只有一个制定标准的委员会,任何其他组织或者个人都可以开发 C 语言的编译器,而这个编译器要遵守哪个 C 语言标准,是 100% 遵守还是部分遵守,并没有强制性的措施,也没有任何约束。 > [!NOTE] > > - ① 各个厂商可以根据自己的利益和喜好来开发编译器。 > - ② 市场和用户的选择通常是推动编译器开发者遵循标准的主要动力。 * 并且,不同硬件平台之间也存在差异,这会导致内存管理方式、寄存器、指令集等都有所不同,为了确保 C 语言程序能在这些硬件平台运行,就得针对该平台开发/定制不同的编译器。 > [!NOTE] > > - ① 上述的情况,在单片机和嵌入式领域更加常见。 > - ② 总体而言,C 语言具有开放性,并且要适应不同的硬件平台,这使得不同厂商可以根据自己的需求来进行个性化开发/定制。 * 这也导致了一个非常棘手的问题,有的编译器遵守较新的 C 语言标准,有的编译器只能遵守较老的 C 语言标准,有的编译器还进行了很多扩展,比如: * GCC、LLVM/Clang 更新非常及时,能够支持最新的 C 语言标准。 * MSVC 更新比较缓慢,迟迟不能支持新标准,例如:VC6.0、VS2010 都在使用 C89 标准,VS2015 部分支持 C99 标准。 > [!NOTE] > > 微软官方给出的答复:最新的标准已经在 C++ 中支持了,C 语言就没必要再重复了。 # 第二章:宏定义 ## 2.1 概述 # 第三章:带参数的宏定义 # 第四章:文件包含 # 第五章:条件编译