嵌入式之C/C++(四)预处理

在嵌入式C/C++开发中,预处理阶段虽然不参与真正的编译和链接,但却是面试中出现频率极高、同时又最容易被忽略的一部分。很多看似"诡异"的Bug,本质都源于对预处理机制理解不清。

1 什么是预处理?它在编译流程中的位置?

C/C++程序的构建流程如下:

bash 复制代码
预处理 → 编译 → 汇编 → 链接

预处理阶段主要完成:

  • 宏替换(#define
  • 条件编译(#ifdef/#ifndef/#if
  • 头文件展开(#include
  • 生成最终送入编译器的源码

⚠️重点:预处理不做语法检查,只是"机械式文本替换"

2 #error预处理指令的作用?

2.1 #error的作用?

#error 用于在预处理阶段主动制造一个编译错误,并输出自定义错误信息,编译会立即终止。

语法:

cs 复制代码
#error error-message

2.2 使用场景:检测宏是否被定义?

在大型嵌入式工程中:

  • 宏可能来自 Makefile
  • 也可能来自系统头文件
  • 有时你并不确定某个宏是否已经定义

示例:

cs 复制代码
#ifdef XXX
    #error "XXX has been defined"
#else
    // normal code
#endif

👉 如果编译报错并输出 XXX has been defined,说明宏 XXX 已存在

3 定义常量:#define还是const?

一句话总结:

能用const,就不用#define

3.1 本质区别?

| 对比项 | #define | const |
| 处理阶段 | 预处理 | 编译期 |
| 原理 | 文本替换 | 真正的变量 |
| 是否分配内存 | ❌ | ✅ |
| 类型检查 | ❌ | ✅ |

调试支持

3.2 深度理解

cs 复制代码
#define MAX 100
const int max = 100;
  • MAX
    • 只是一个符号替换
    • 编译后根本不存在
  • max
    • 有类型
    • 有地址
    • 可以被调试器看到

👉 嵌入式开发中推荐使用 const 定义常量

4 typedef#define的区别?

4.1 原理不同

  • #define

    👉 预处理阶段,纯文本替换,不做任何检查

  • typedef

    👉 编译阶段,有完整类型系统支持

示例:

cs 复制代码
#define PI 3.1415926
typedef int INTEGER;

4.2 功能不同?

  • typedef
    • 类型起别名
    • 支持 struct / pointer / function pointer
  • #define
    • 常量
    • 宏函数
    • 编译开关

📌 typedef 的重要用途之一:定义机器无关类型

cs 复制代码
typedef long double REAL;

4.3 作用域不同?

cs 复制代码
void fun()
{
    #define A int
}

void gun()
{
    A x;   // 合法!宏没有作用域
}

⚠️ typedef 有作用域,宏没有

4.4 指针问题?

cs 复制代码
#define INTPTR1 int*
typedef int* INTPTR2;

INTPTR1 p1, p2;   // int* p1, p2 → p2是int
INTPTR2 p3, p4;   // p3、p4 都是 int*

👉 原因:

  • #define 是"拆开替换"
  • typedef 是"整体类型"

4.5 const + typedef 的陷阱?

cs 复制代码
const INTPTR1 p1 = &a;   // 指向常量的指针
const INTPTR2 p2 = &b;   // 指针常量

📌 面试总结口诀:

typedef 定义的是"类型",const 修饰的是"整个类型"

5 经典宏题:一年有多少秒?

cs 复制代码
#define SECOND_PER_YEAR (60 * 60 * 24 * 365UL)

📌 考点:

  • 宏要加括号
  • 使用 UL 防止整型溢出

6 #include <>#include ""的区别?

| 写法 | 搜索路径 |
| <file.h> | 先系统目录 |

"file.h" 先当前目录,再系统目录

👉 面试标准回答:

  • 系统头文件用 < >
  • 自定义头文件用 " "

7 头文件的作用?

  • ①提供接口,隐藏实现

    • 只暴露函数声明
    • 源码不必给用户
  • ②增强类型安全

    • 编译期检查函数参数、返回值
    • 极大降低调试成本

8 为什么不能在头文件中定义静态变量?

❌ 不推荐,原因如下:

  • 每个 .c 文件都会生成一份
  • 造成 资源浪费
  • 可能引发 逻辑错误

👉 原则:

头文件只放声明,不放定义

9 标准宏MIN?

cs 复制代码
#define MIN(A, B) ((A) <= (B) ? (A) : (B))

📌 面试延伸:

  • 为什么要加括号?
  • 宏参数有副作用怎么办?

10 不使用流程控制打印 1~1000?

方法一:宏嵌套

cs 复制代码
#define I printf("%3d", i++)
#define N printf("\n")
#define L I,I,I,I,I,I,I,I,I,I,N
#define P L,L,L,L,L,L,L,L,L,L
#define B P,P,P,P,P,P,P,P,P,P

方法二:递归宏(简写)

cs 复制代码
#define A(x) x;x;x;x;x;x;x;x;x;x
A(A(A(printf("%d ", n++))))

📌 考察点:

  • 宏展开
  • 预处理理解深度

11 总结

  • #define 是文本替换,typedef 是类型别名
  • const#define 更安全
  • 宏没有作用域,typedef 有
  • 指针 + typedef 是面试重灾区
  • 头文件只放声明,不放定义
相关推荐
历程里程碑2 小时前
Linux 18 进程控制
linux·运维·服务器·开发语言·数据结构·c++·笔记
爱装代码的小瓶子2 小时前
【c++与Linux基础】文件篇(5)- 文件管理系统:
linux·开发语言·c++
梵刹古音2 小时前
【C语言】 数组基础与地址运算
c语言·开发语言·算法
xu_yule2 小时前
网络和Linux网络-15(IO多路转接)reactor编程-服务器
linux·运维·服务器·c++
Howrun7772 小时前
C++_错误处理
开发语言·c++
近津薪荼2 小时前
优选算法——滑动窗口3(子数组)
c++·学习·算法
方便面不加香菜2 小时前
c++入门基础
c++
雍凉明月夜2 小时前
瑞芯微RV1106G3板端部署
c++·人工智能·深度学习
小龙报2 小时前
【51单片机】串口通讯从入门到精通:原理拆解 + 参数详解 + 51 单片机实战指南
c语言·驱动开发·stm32·单片机·嵌入式硬件·物联网·51单片机