c/docs/notes/02_c-leap/06_xdx/index.md

171 lines
7.0 KiB
Markdown
Raw Normal View History

2024-10-09 02:52:49 +02:00
# 第一章:基本介绍
## 1.1 回顾 C 语言的编译过程
* C 语言的编译过程,如下所示:
![](./assets/1.png)
* 过程 ① :编写(编辑)源代码,即:编写 C 语言源程序代码,并以文件的形式存储在磁盘中。
> [!NOTE]
>
> 源程序需要以 `.c` 作为扩展名。
2024-10-09 02:53:51 +02:00
* 过程 ② :编译,即:将 C 语言源程序转换为`目标程序(或目标文件)`。如果程序没有错误,没有任何提示,就会生成一个扩展名为 `.obj``.o` 的二进制文件。C 语言中的每条可执行语句经过编译之后,最终都会转换为二进制的机器指令。
2024-10-09 02:52:49 +02:00
2024-10-09 02:53:51 +02:00
> [!NOTE]
>
> - ① 其实,`编译阶段`包含了`预处理`、`编译`和`汇编`。
> - ② `预处理`是编译过程的第一个阶段。在这个阶段,预处理器处理源代码中的指令(例如:`#include`、`#define`等),主要任务包括:
> - 头文件包含:将头文件的内容插入到源文件中。例如:`#include <stdio.h>`会被替换为`stdio.h`文件的内容。
> - 宏展开:替换宏定义。例如:`#define PI 3.14`会将代码中的`PI`替换为`3.14`。
> - 条件编译:根据条件指令(如:`#ifdef`、`#ifndef`)有选择地编译代码。
> - 删除代码中的注释,但是不会进行语法检查。
> - 预处理完成后,生成一个扩展名为`.i`的中间文件。
> - ③ `编译`是将预处理后的源代码转换为汇编代码的过程。在这个阶段,编译器会检查代码的语法和语义,将其转换为目标机器的汇编语言,生成一个扩展名为`.s`的汇编文件。
> - ④ `汇编`是将汇编代码转换为机器代码(也称为目标代码或目标文件)的过程。在这个阶段,汇编器将汇编指令转换为二进制机器指令,生成一个扩展名为`.o`或 `.obj`的目标文件。
* 过程 ③ :链接(连接),即:将编译形成的目标文件 `*.obj``*.o`和库函数以及其他目录文件`链接`,形成一个统一的二进制文件 `*.exe`
> [!NOTE]
>
> - 为什么需要链接库文件?
> - 因为我们的 C 程序会使用 C 程序库中的内容,如:`<stdio.h>` 中的 `printf()` 函数,这些函数不是程序员自己写的,而是 C 程序库中提供的因此需要链接。其实在链接过程中还会加入启动代码这个启动代码和系统相关Linux 下主要有 crt0.c、crti.c 等,它们设置堆栈后,再调用 main() 函数)负责初始化程序运行时的环境。
2024-10-09 02:52:49 +02:00
2024-10-09 03:08:59 +02:00
- 过程 ④ :执行,即:有了可执行的 `*.exe`文件,我们就可以在控制台上执行运行此 `*.exe` 文件。
> [!NOTE]
>
> 如果`修改`了源代码,还需要重新`编译`、`链接`,并生成新的 `*.exe`文件,再执行,方能生效。
## 1.2 和其他编程语言的对比
* 在编译和链接之前C 语言需要对源文件进行一些文本方面的操作,如:删除代码中的注释,但是不会进行语法检查、头文件包含、宏展开、条件编译等,这个过程叫做预处理,由`预处理程序(预处理器)`完成。
* 较之其他的编程语言Java 、C# 等C/C++ 语言更依赖预处理器,所以在阅读或开发 C/C++ 程序的过程中,可能会接触到大量的预处理指令,如:`#include`、`#define` 等。
2024-10-09 03:45:00 +02:00
## 1.3 预处理指令
* 预处理过程中会执行预处理指令,预处理指令以 `#`开头,如:`#include` 等,用于指导预处理器执行不同的任务。
* 预处理器有如下的特点:
* ① 预处理指令应该放在代码的开头部分。
* ② 预处理指令都以 `#`开头,指令前面可以有空白字符(比如空格或制表符),`#`和指令的其余部分之间也可以有空格,但是为了兼容老的编译器,一般不留空格。
```c
// 推荐写法
#include <stdio.h>
```
```c
// 不推荐写法
#include <stdio.h>
# include <stdio.h>
```
2024-10-09 05:12:24 +02:00
* ③ 预处理指令都是一行的,除非在行尾使用反斜杠,将其折行,但强烈不建议这么干。
2024-10-09 03:45:00 +02:00
```c
#include <std\
io.h>
```
* ④ 预处理指令不需要分号作为结束符,指令结束是通过换行符来识别的。
```c
2024-10-09 03:58:23 +02:00
#include <stdio.h>; // 这里有分号会报错
2024-10-09 03:45:00 +02:00
```
2024-10-09 03:58:23 +02:00
```c
#define PI 3.14; // 分号会成为 PI 的值的一部分
```
2024-10-09 03:45:00 +02:00
2024-10-09 03:58:23 +02:00
* ⑤ 预处理指令通常不能写在函数内部,有些编译器的扩展允许将预处理指令写在函数里,但强烈不建议这么干。
2024-10-09 03:45:00 +02:00
2024-10-09 03:58:23 +02:00
```c
int main () {
// 一般不允许写在这里
2024-10-09 05:12:24 +02:00
#include <stdio.h> // [!code warning]
2024-10-09 03:58:23 +02:00
return 0;
}
```
2024-10-09 02:52:49 +02:00
2024-10-09 05:12:24 +02:00
## 1.4 为什么 C 语言需要预处理?
2024-10-09 06:30:15 +02:00
### 1.4.1 概述
* 在实际开发中,有的时候,我们希望自己编写的程序能够跨平台(操作系统)运行,但是可能每个平台提供的系统库函数不同,如:在 Windows 上实现暂停的函数是 `void Sleep(DWORD dwMilliseconds)`,单位是 `ms`,头文件是 `<windows.h>`;而 Linux 上实现暂停的函数是 `unsigned int sleep (unsigned int seconds)`,单位是 `s` ,头文件是 `<unistd.h>`。所以,我们希望在 Windows 上调用的是 `Sleep()` 函数,而在 Linux 上调用的是 `sleep()` 函数,怎么办?
* 这就需要在编译之前对源文件进行处理:如果检测到的平台是 Windows就保留 `Sleep()` 函数而删除 `sleep()` 函数;反之,如果检测到的平台是 Linux则保留 `sleep()` 函数而删除 `Sleep()` 函数。
2024-10-09 05:12:24 +02:00
> [!NOTE]
>
2024-10-09 06:30:15 +02:00
> * ① 这些在编译之前对源文件进行简单加工的过程,就称为`预处理`,即:预先处理、提前处理。
> * ② 之前提过,在 Windows 上推荐使用 `MSYS2` ,就是因为 `MSYS2` 提供了一个兼容层,使得在 Windows 上可以使用类似于 Linux 的环境。并且,`MSYS2` 同时支持 Windows 和类 Unix 的库函数,对我们开发跨平台程序更为友好!!!
### 1.4.2 应用示例
2024-10-09 05:12:24 +02:00
2024-10-09 06:30:15 +02:00
* 需求:开发一个 C 语言程序,让它暂停 5 秒以后再输出内容,并且要求跨平台。
2024-10-09 05:12:24 +02:00
> [!NOTE]
>
2024-10-09 06:30:15 +02:00
> 不同平台下的暂停函数和头文件都不一样,如下所示:
>
> * ① Windows 平台下的暂停函数的原型是`void Sleep(DWORD dwMilliseconds)`(注意 S 是大写的),参数的单位是 `ms`,位于 `<windows.h>` 头文件。
> * ② Linux 平台下暂停函数的原型是`unsigned int sleep (unsigned int seconds)`,参数的单位是 `s`,位于 `<unistd.h>` 头文件。
2024-10-09 05:12:24 +02:00
2024-10-09 06:30:15 +02:00
* 示例:
```c
#include <stdio.h>
// 不同的平台下引入不同的头文件
#if _WIN32 // 识别windows平台
#include <windows.h>
#elif __linux__ // 识别linux平台
#include <unistd.h>
#endif
int main() {
// 不同的平台下调用不同的函数
#if _WIN32 // 识别windows平台
Sleep(5000);
#elif __linux__ // 识别linux平台
sleep(5);
#endif
puts("Hello World\n");
return 0;
}
```
2024-10-09 05:12:24 +02:00
2024-10-09 02:52:49 +02:00
# 第二章:宏定义
2024-10-09 05:12:24 +02:00
## 2.1 概述
2024-10-09 02:52:49 +02:00
# 第三章:带参数的宏定义
# 第四章:文件包含
# 第五章:条件编译