C语言-嵌入式开发C语言常用预处理指令合集

本章详细讲解嵌入式开发中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__ */

制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!

相关推荐
上海安当技术2 小时前
敏感数据怎么防拖库?信封加密(DEK+KEK 二层密钥)架构设计与 Java 实战
java·开发语言·python
我是唐青枫2 小时前
Java JCommander 实战详解:用注解解析命令行参数
java·开发语言
大鹏说大话2 小时前
C++ 内存布局详解:类、虚函数、虚表底层原理
java·开发语言
键盘会跳舞2 小时前
C++:容器适配器 queue 的源码级拆解
开发语言·c++·queue
YuforiaCode3 小时前
第十六届蓝桥杯 2025 C/C++组 数列差分
c语言·c++·蓝桥杯
Loge编程生活3 小时前
打卡信奥刷题(3449)用C++实现信奥题 P10429 [蓝桥杯 2024 省 B] 拔河
开发语言·数据结构·c++·算法·青少年编程
在世修行4 小时前
从零打造 C# 工业视觉检测系统(八):Modbus RTU 协议解析与 PLC 剔除控制实战
开发语言·c#
sunburn-5 小时前
Java 队列全面详解:从入门到面试实战
java·开发语言·汇编·ide·idea
AI直播技术杂谈5 小时前
AI直播推流链路中延迟优化的通用技术方案
开发语言·人工智能·php