本章详细讲解嵌入式开发中C语言常用预处理指令合集
C语言(含嵌入式C)的预处理指令以 # 开头、行末不加分号,在编译前由预处理器纯文本处理。按功能可分为 七大类,下面逐一详解。
1、宏定义类
2、文件包含类
3、条件编译类(核心掌握!!!)
4、编译控制类
5、行号与调试辅助类
6、预定义宏(编译器自动提供)
7、运算副:# 和 ##
一、宏定义类
1. #define ------ 宏定义
最核心的预处理指令,本质是纯文本替换,无类型检查、无语法检查。
| 形式 | 示例 | 说明 |
|---|---|---|
| 对象宏(常量) | #define PI 3.1415926 |
替换所有出现的 PI |
| 函数宏(带参) | #define MAX(a,b) ((a)>(b)?(a):(b)) |
展开为纯文本 |
| 空参宏 | #define GET_STATUS() (REG_STATUS) |
模拟无参函数 |
| 多行宏 | 见下方 | 需用 do{...}while(0) 包裹 |
踩坑点:
cpp
// 错误:优先级陷阱
#define VAL 1 + 2
int x = VAL * 3; // 展开为 1 + 2 * 3 = 7,不是9!
// 正确
#define VAL (1 + 2)
// 错误:参数带副作用
#define SQUARE(x) x * x
int a = SQUARE(i++); // i++ 被展开两次,行为未定义!
// 正确:参数和整体都加括号
#define SQUARE(x) ((x) * (x))
// 多行宏必须用 do-while(0) 包裹
#define UART_SEND(c) do { \
while(!(UART->SR & TXE)); \
UART->DR = (c); \
} while(0)
2. #undef ------ 取消宏定义
用途:临时覆盖第三方库宏、避免头文件间宏污染、调试时切换配置。
cpp
#define BUF_SIZE 100
// ... 使用 BUF_SIZE ...
#undef BUF_SIZE // 此后 BUF_SIZE 不再有效
#define BUF_SIZE 200 // 可重新定义
二、文件包含类
3. #include ------ 文件包含
把指定文件的内容原封不动复制粘贴到当前位置。
| 写法 | 查找路径 | 典型用途 |
|---|---|---|
#include <stdio.h> |
系统/标准库目录 | 标准库头文件 |
#include "my_driver.h" |
先当前目录,再系统目录 | 自定义头文件 |
嵌入式开发例子:
cpp
// 头文件保护(Include Guard)------ 必须写!
#ifndef __UART_DRIVER_H__
#define __UART_DRIVER_H__
void uart_init(uint32_t baud);
void uart_send(uint8_t data);
#endif /* __UART_DRIVER_H__ */
三、条件编译类(核心!!!)
条件编译决定哪些代码进入编译 ,被丢弃的代码完全不参与后续编译(与运行时 if 有本质区别)。
4. #if ------ 整型常量表达式条件编译
支持关系运算符(>、<、==、!=)和逻辑运算符(&&、||)。
cpp
#if (SYSTEM_CLOCK >= 72000000)
#define USE_HIGH_SPEED 1
#elif (SYSTEM_CLOCK >= 36000000)
#define USE_HIGH_SPEED 0
#else
#define USE_HIGH_SPEED 0
#endif
5. #ifdef ------ 宏已定义则编译
cpp
#ifdef STM32F103
#include "stm32f1xx_hal.h"
#define LED_PIN GPIO_PIN_5
#endif
6. #ifndef ------ 宏未定义则编译
cpp
#ifndef DEBUG_MODE
#define DEBUG_MODE 0 // 默认关闭调试
#endif
#ifndef __CONFIG_H__
#define __CONFIG_H__
// ...
#endif
7. #else ------ 否定分支
cpp
#ifdef USE_DMA
// DMA方式发送
#else
// 轮询方式发送
#endif
8. #elif ------ else if 分支
cpp
#if MCU_TYPE == 1
// 适配型号A
#elif MCU_TYPE == 2
// 适配型号B
#elif MCU_TYPE == 3
// 适配型号C
#endif
9. #endif ------ 结束条件编译块
每个 #if/#ifdef/#ifndef 必须配对 #endif。
10. defined() 运算符(配合 #if 使用)
cpp
#if defined(USE_RTOS) && defined(USE_TICKLESS)
// RTOS + 低功耗模式
#endif
// 也可写作 #ifdef 的简写
#ifdef USE_RTOS // 等价于 #if defined(USE_RTOS)
四、编译控制类
11. #error ------ 强制编译报错
在不满足条件时终止编译并输出自定义错误信息:
嵌入式典型用法:防止配置遗漏导致静默编译通过。
cpp
#if !defined(MCU_TYPE)
#error "请在编译前定义 MCU_TYPE!"
#endif
#if (FLASH_SIZE < 0x10000)
#error "Flash 空间不足,无法运行此固件!"
#endif
12. #warning ------ 输出编译警告(部分编译器支持)
注意:#warning 不是 C 标准,GCC/Clang 支持,MSVC 不支持。
cpp
#warning "此模块已废弃,请迁移到新版驱动"
13. #pragma ------ 编译器特定指令
向编译器发出特定控制指令,无统一标准,各编译器含义不同:
| 用法 | 说明 |
|---|---|
#pragma once |
防止头文件重复包含(非标准但广泛支持) |
#pragma pack(1) |
设置结构体1字节对齐(嵌入式通信协议常用) |
#pragma GCC optimize("O2") |
指定优化等级 |
#pragma interrupt ISR_Handler |
指定中断服务函数(Keil/IAR) |
#pragma data_seg(".fast_data") |
指定变量段(IAR) |
cpp
// 嵌入式结构体对齐示例
#pragma pack(push, 1)
typedef struct {
uint8_t cmd;
uint16_t len;
uint32_t addr;
} Packet_t;
#pragma pack(pop)
// sizeof(Packet_t) == 7
五、行号与调试辅助类
14. #line ------ 修改行号和文件名
用途:自动代码生成工具(如协议解析器生成器)让编译器报告的错误指向源文件而非生成文件。嵌入式中较少直接使用,但调试时有间接作用。
cpp
#line 100 "generated_code.c"
六、预定义宏(编译器自动提供)
注:这些不是我们自己写的,而且编译器预定义的,常用于平台检测
| 宏名 | 含义 |
|---|---|
__FILE__ |
当前源文件名(字符串) |
__LINE__ |
当前行号(整数) |
__DATE__ |
编译日期 "Mmm dd yyyy" |
__TIME__ |
编译时间 "hh:mm:ss" |
__STDC__ |
是否符合标准C(值为1) |
__FUNCTION__ |
当前函数名 |
__STDC_VERSION__ |
C标准版本(如 201112L 表示C11) |
__cplusplus |
C++编译时定义 |
cpp
// 嵌入式调试打印常用
printf("Error at %s:%s:%d\n", __FILE__,__FUNCTION__,_LINE__);
七、运算符: # 和
这不是独立指令,但在#define 中常用:
15. # ------ 字符串化运算符
将宏参数转为字符串字面量:
cpp
#define STRINGIFY(x) #x
#define VERSION_STR STRINGIFY(1.2.3) // "1.2.3"
#define SHOW(x) printf(#x " = %d\n", x)
SHOW(LED_COUNT); // 输出: LED_COUNT = 4
16. ## ------ 标记连接运算符
将两个标识符拼接成一个:
cpp
#define GPIO_PIN(port, pin) GPIO##port##_PIN##pin
#define LED_INIT() GPIO_PIN(A, 5) // 展开为 GPIOA_PIN5
// 寄存器地址动态拼接
#define REG_ADDR(periph, offset) ((volatile uint32_t*)(PERIPH_BASE_##periph + offset))
总结
预处理指令是嵌入式C的"编译期开关",所有判断在编译前完成、不占用任何运行资源。掌握这十几条指令,你就掌握了嵌入式代码跨平台、可配置、高可维护的核心武器。
| 指令 | 类别 | 核心作用 | 嵌入式典型场景 |
|---|---|---|---|
#define |
宏定义 | 文本替换/常量/函数宏 | 寄存器定义、位操作封装 |
#undef |
宏定义 | 取消宏 | 临时覆盖、避免冲突 |
#include |
文件包含 | 插入头文件内容 | 驱动头文件、模块化 |
#if |
条件编译 | 表达式条件编译 | 时钟频率适配 |
#ifdef |
条件编译 | 宏已定义则编译 | 平台检测、功能开关 |
#ifndef |
条件编译 | 宏未定义则编译 | 头文件保护、默认配置 |
#else |
条件编译 | 否定分支 | 调试/发布切换 |
#elif |
条件编译 | 多分支条件 | 多型号适配 |
#endif |
条件编译 | 结束条件块 | 所有条件编译的结束 |
#error |
编译控制 | 强制编译报错 | 配置缺失检测 |
#warning |
编译控制 | 输出警告 | 废弃模块提示 |
#pragma |
编译控制 | 编译器特定指令 | 结构体对齐、中断函数 |
#line |
调试辅助 | 修改行号信息 | 代码生成器 |
# / ## |
运算符 | 字符串化/标记连接 | 调试打印、寄存器拼接 |
嵌入式实战:完整示例
cpp
/*========== driver.h ==========*/
#ifndef __DRIVER_H__ // 头文件保护
#define __DRIVER_H__
// 平台检测
#if defined(STM32F103xB)
#define SYSTEM_CLOCK 72000000UL
#include "stm32f1xx_hal.h"
#elif defined(STM32F407xx)
#define SYSTEM_CLOCK 168000000UL
#include "stm32f4xx_hal.h"
#else
#error "未定义目标MCU型号!" // 强制报错
#endif
// 功能开关
#ifndef USE_DMA
#define USE_DMA 1
#endif
// 寄存器操作宏(带括号防陷阱)
#define SET_BIT(reg, bit) ((reg) |= (1U << (bit)))
#define CLR_BIT(reg, bit) ((reg) &= ~(1U << (bit)))
#define READ_BIT(reg, bit) (((reg) >> (bit)) & 1U)
// 多行函数宏
#define UART_WAIT_TX(uart) do { \
while (!((uart)->SR & (1U << 7))) {} \
} while(0)
#endif /* __DRIVER_H__ */
制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!