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

嵌入式开发中,C/C++ 预处理(Preprocessor) 是非常核心且经常被低估的部分,尤其在资源受限的 MCU 项目中,它直接影响代码体积、可维护性、可移植性、调试难度和最终生成的 .hex / .bin 大小。

下面从嵌入式视角给你一个系统、实用的预处理详解(2026 年视角,适用于 Keil/IAR/STM32CubeIDE/GCC 等主流工具链)。

1. 预处理在编译流程中的位置(嵌入式必知)

C/C++ 编译完整流程(嵌入式项目最常见顺序):

  1. 预处理(Preprocessor) → .c/.cpp → .i(文本文件,展开后的源代码)
  2. 编译(Compiler) → .i → .s(汇编代码)
  3. 汇编(Assembler) → .s → .o(目标文件)
  4. 链接(Linker) → .o + .lib/.a → .elf / .axf / .out
  5. 后处理(fromelf / objcopy 等) → .hex / .bin / .srec

嵌入式关键点
预处理阶段决定了最终代码有多少“肉”被塞进 MCU Flash。
滥用宏 → 代码膨胀、调试困难
合理用条件编译 → 支持多款板子/不同配置、减小 bin 大小

2. 所有常用预处理指令(嵌入式高频排序)

优先级指令嵌入式最常见用途示例(MCU 项目典型写法)注意事项 / 坑点
1#include头文件包含#include "stm32f4xx.h"
#include <stdint.h>
“” vs <> 路径区别、重复包含防护
2#define / #undef宏定义、常量、寄存器别名、位操作简化#define LED_GPIO_PORT GPIOB
#define LED_PIN GPIO_PIN_5
宏参数要加括号、防重定义
3#ifdef / #ifndef条件编译(最重要!)#ifdef DEBUG
printf(...);
#endif
配合 -DDEBUG 编译选项
4#if / #elif / #else更复杂的条件判断(版本、芯片型号、频率等)#if defined(STM32F407xx) && (HSE_VALUE == 8000000)优先用 #ifdef 简单场景
5#endif结束条件编译块必须配对,建议写注释
6#pragma编译器特定指令(对齐、pack、优化、警告抑制等)#pragma pack(1)
#pragma GCC optimize("O3")
不同编译器写法不同
7#error编译期报致命错误#if !defined(__CC_ARM) && !defined(__GNUC__)
#error "Only ARMCC or GCC supported"
强制约束编译环境
8#warning编译期警告#warning "This driver is deprecated"提醒开发者
9#line修改行号(很少用,调试工具生成代码时常见)基本不手写
10## / #宏粘贴运算符、字符串化运算符#define STR(x) #x
#define CONCAT(a,b) a##b
调试宏展开神器

3. 嵌入式项目中最实用的 10 种预处理写法(强烈建议掌握)

1. 防止头文件重复包含(Include Guard / 宏防护)

现代写法(推荐):

#pragma once          // 大部分现代编译器支持,简洁、安全

// 或者传统写法(兼容所有编译器)
#ifndef __MY_DRIVER_H__
#define __MY_DRIVER_H__

// 头文件内容

#endif /* __MY_DRIVER_H__ */

2. 寄存器 / 外设 宏定义(最常见)

#define GPIOA_BASE    (AHB1PERIPH_BASE + 0x0000UL)
#define GPIOA         ((GPIO_TypeDef *) GPIOA_BASE)

#define LED_ON()      do { GPIOA->BSRR = GPIO_PIN_5; } while(0)
#define LED_OFF()     do { GPIOA->BSRR = (uint32_t)GPIO_PIN_5 << 16; } while(0)
#define LED_TOGGLE()  do { GPIOA->ODR ^= GPIO_PIN_5; } while(0)

3. 根据芯片型号 / 系列 条件编译

#if defined(STM32F103xB) || defined(STM32F103xE)
    #define FLASH_PAGE_SIZE   1024U
#elif defined(STM32F407xx) || defined(STM32F429xx)
    #define FLASH_PAGE_SIZE   2048U
#else
    #error "Unsupported MCU series"
#endif

4. Debug / Release 切换(超级实用)

#ifdef DEBUG
    #define LOG_INFO(fmt, ...)   printf("[INFO] " fmt "\n", ##__VA_ARGS__)
    #define ASSERT(x)            do { if(!(x)) { __BKPT(0); } } while(0)
#else
    #define LOG_INFO(fmt, ...)   ((void)0)
    #define ASSERT(x)            ((void)0)
#endif

编译时加选项:
Keil → C/C++ → Preprocessor Symbols → Define: DEBUG
GCC → -DDEBUG

5. 位操作宏(嵌入式最爱,避免笔误)

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT)    ((REG) & (BIT))
#define CLEAR_REG(REG)        ((REG) = (0U))

STM32 HAL 库大量使用这种写法。

6. 不同编译器兼容

#if defined(__GNUC__)           // GCC / ARM GCC
    #define __WEAK   __attribute__((weak))
    #define __PACKED __attribute__((__packed__))
#elif defined(__CC_ARM)         // Keil / ARMCC
    #define __WEAK   __weak
    #define __PACKED __packed
#elif defined(_MSC_VER)
    #define __WEAK
    #define __PACKED
#else
    #warning "Unknown compiler, some attributes may not work"
#endif

7. 字符串化和连接(调试宏神器)

#define STRINGIFY(x)   #x
#define TO_STRING(x)   STRINGIFY(x)

#define CONCAT(a,b)    a##b

// 用法示例
#define PIN_NAME PA5
printf("Pin is " TO_STRING(PIN_NAME) "\n");   // 输出:Pin is PA5

4. 嵌入式预处理常见坑 & 最佳实践(2026 年总结)

后果解决方案
宏参数没括号运算符优先级错误#define MUL(a,b) ((a)*(b))
宏展开过长Flash 爆炸、调试地狱优先用 inline 函数(C99/C++)
条件编译不配对语法错误#endif /* XXX */ 加注释
在头文件里 #define 全局宏污染命名空间只在 .c 文件或局部使用
滥用 #pragma pack结构体对齐错乱只在通信协议结构体上用,并恢复默认 #pragma pack()
没用 __attribute__((unused))(void)x编译警告抑制未使用参数警告

5. 调试预处理展开的终极技巧

GCC / Clang:

arm-none-eabi-gcc -E -dM yourfile.c > preprocessed.i   # 只看宏定义
arm-none-eabi-gcc -E yourfile.c > expanded.c           # 看完整展开

Keil:Options → C/C++ → Preprocessor → “Preprocess” 勾选 → 查看 .i 文件

重阳,嵌入式预处理这一块你现在最想深入哪个方向?

  • 多工程 / 多芯片支持的条件编译写法?
  • 宏 vs inline vs const 的性能 & Flash 对比?
  • 如何写一套跨 STM32F1/F4/H7 的外设驱动宏?
  • 还是想看一个完整工程的预处理实践案例?

随时说,我们继续深挖~

文章已创建 4357

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部