嵌入式之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 是面试重灾区
  • 头文件只放声明,不放定义
相关推荐
ShineWinsu16 小时前
对于C++:继承的解析—上
开发语言·数据结构·c++·算法·面试·笔试·继承
小付同学呀16 小时前
C语言学习(五)——输入/输出
c语言·开发语言·学习
梦幻精灵_cq16 小时前
学C之路:不可或缺的main()主函数框架(Learn-C 1st)
c语言·开发语言
消失的旧时光-194317 小时前
C++ 多线程与并发系统取向(二)—— 资源保护:std::mutex 与 RAII(类比 Java synchronized)
java·开发语言·c++·并发
抓饼先生18 小时前
iceoryx编译和验证
linux·c++·零拷贝·iceoryx
王老师青少年编程18 小时前
2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第2题)
c++·题解·真题·初赛·信奥赛·csp-s·提高组
你的冰西瓜19 小时前
C++ STL算法——排序和相关操作
开发语言·c++·算法·stl
今儿敲了吗19 小时前
29| 高考志愿
c++·笔记·学习·算法
浅念-20 小时前
C++ 模板进阶
开发语言·数据结构·c++·经验分享·笔记·学习·模版
紫陌涵光20 小时前
77. 组合
c++·算法·leetcode·深度优先