一、预编译主要处理源代码文件(.cpp)。
处理规则:
1、删除所有的#define,展开所有的宏定义。
2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,头文件中包含其他头文件。
4、删除所有的注释,“//”和“/**/”。
5、保留所有的#pragma 编译器指令,编译器需要用到他们
6、添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
经预编译之后,生成xxx.ii文件(编译单元)。
例子
我们有两个文件:
-
头文件
math_utils.h
(声明和定义一些数学工具函数) -
源文件
main.cpp
(包含头文件并调用函数)
文件内容如下:
1. 头文件 math_utils.h
// math_utils.h
#ifndef MATH_UTILS_H // 头文件守卫,防止重复包含
#define MATH_UTILS_H
// 声明一个函数
int add(int a, int b);
// 定义一个内联函数
inline int multiply(int a, int b) {
return a * b;
}
#endif // MATH_UTILS_H
2. 源文件 main.cpp
// main.cpp
#include "math_utils.h"
#include <iostream>
int main() {
std::cout << "Add: " << add(2, 3) << std::endl;
std::cout << "Multiply: " << multiply(2, 3) << std::endl;
return 0;
}
编译时的“粘贴”过程
当编译器处理 main.cpp
时,遇到 #include "math_utils.h"
,会执行以下操作:
步骤 1:预处理阶段的文本替换
编译器将 math_utils.h
的内容逐字复制到 main.cpp
中 #include
的位置,形成一个新的临时文件(称为“编译单元”)。替换后的 main.cpp
实际内容如下:
// main.cpp(预处理后的版本)
// ↓↓↓ 以下是 math_utils.h 的内容 ↓↓↓
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
inline int multiply(int a, int b) {
return a * b;
}
#endif // MATH_UTILS_H
// ↑↑↑ math_utils.h 的内容结束 ↑↑↑
#include <iostream> // 标准库头文件也会被替换(此处省略细节)
int main() {
std::cout << "Add: " << add(2, 3) << std::endl;
std::cout << "Multiply: " << multiply(2, 3) << std::endl;
return 0;
}
步骤 2:处理头文件守卫
-
第一次遇到
#ifndef MATH_UTILS_H
时,MATH_UTILS_H
未定义,因此保留后续内容。 -
如果同一头文件被多次包含(例如在大型项目中),第二次时
MATH_UTILS_H
已定义,头文件内容会被跳过,避免重复声明。 -
#pragma once 也是为了防止有头文件被重复引用。
步骤 3:获得编译单元(Translation Unit)
此时,预处理后的 main.cpp
已经是一个完整的编译单元,包含:
-
头文件中的函数声明(
add
)和内联函数定义(multiply
)。 -
源文件中的
main
函数逻辑。 -
其他被包含的头文件(如
<iostream>
)。