编译原理(3):预编译

一、预编译主要处理源代码文件(.cpp)。

处理规则:

1、删除所有的#define,展开所有的宏定义。 
2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。 
3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,头文件中包含其他头文件。 
4、删除所有的注释,“//”和“/**/”。 
5、保留所有的#pragma 编译器指令,编译器需要用到他们
6、添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。

      经预编译之后,生成xxx.ii文件(编译单元)。

例子

我们有两个文件:

  1. 头文件 math_utils.h(声明和定义一些数学工具函数)

  2. 源文件 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 已经是一个完整的编译单元,包含:

  1. 头文件中的函数声明(add)和内联函数定义(multiply)。

  2. 源文件中的 main 函数逻辑。

  3. 其他被包含的头文件(如 <iostream>)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值