C/C 全部预处理指令大全完整实用单片机开发高频版预处理指令是C/C编译器的「编译前处理命令」所有指令都以#开头在代码编译阶段之前执行不参与程序运行、不占用单片机的ROM/RAM资源是嵌入式51/STM32/单片机开发中最常用、最核心的语法之一。✅ 核心特点所有预处理指令行尾不需要加分号;这是新手最容易犯的错误✅ 适用场景单片机开发中预处理指令的使用频率远超PC端编程几乎所有工程都会用到必须吃透。一、先明确预处理指令的执行时机程序的编译执行分3步预处理指令在第一步完成执行后编译器才会开始真正的编译、链接预处理阶段执行所有#指令→编译阶段把C代码转汇编/机器码→链接阶段生成可执行文件二、最全预处理指令分类讲解按「使用频率优先级」排序重点标★嵌入式必掌握所有预处理指令分为6大类我把单片机开发高频用的标红加粗新手必学的标★完全贴合你的单片机开发场景学完直接能用✅ 第一类【宏定义相关】★★★★★ 最高频嵌入式每天都用核心作用定义宏、取消宏是单片机里定义引脚、常量、简化代码的核心指令没有之一。1.#define 宏名 [内容]定义宏无参宏最基础、最常用的指令用来定义符号常量、引脚别名、功能标识。// 单片机经典用法1定义引脚51单片机#defineLED_PINP1_0// 经典用法2定义常量避免魔法数字改值只需改一处#defineDELAY_TIME800#defineBAUD_RATE9600// 经典用法3定义功能开关配合你学的#ifdef使用#defineDEBUG1#defineUSE_UART12.#define 宏名(参数1,参数2) 表达式带参数宏宏函数相当于「简化版的函数」编译前直接替换代码无函数调用开销单片机中用来写简单的运算/操作极致节省资源。// 例子1取两个数的最大值#defineMAX(a,b)(a)(b)?(a):(b)// 例子2数码管消影的空指令简化代码#defineNOP()_nop_()✔️ 注意带参宏的参数必须加括号避免运算优先级错误3.#undef 宏名取消宏定义作用删除已经被#define定义的宏取消后该宏就视为「未定义」状态常和#ifdef配合使用。#defineTEST1#ifdefTESTLED_PIN1;// 这段会编译#endif#undefTEST// 取消TEST的宏定义#ifdefTESTLED_PIN0;// 这段不会编译因为TEST已被取消#endif✅ 第二类【条件编译相关】★★★★★ 最高频你问的#ifdef就在这核心作用选择性编译代码—— 满足条件就编译某段代码不满足就直接剔除被剔除的代码不会生成机器码完全不占存储空间。这是单片机开发的「灵魂指令」解决硬件适配、功能裁剪、调试开关、跨平台移植所有核心问题你之前学的#ifdef只是其中一个完整全家桶如下全部要掌握1.#ifdef 宏名#endif「判断宏是否被定义」✅ 你已掌握如果宏名被#define定义过不管值是1/0/任意内容就编译中间的代码。当代码需要根据不同的操作系统或环境编译不同部分时可以用 #if 配合 #elif 来实现多路分支#ifdefined(_WIN32)// Windows 平台的代码#includewindows.h#elifdefined(__linux__)// Linux 平台的代码#includeunistd.h#else// 其他未知平台的代码#error不支持的操作系统#endif2.#ifndef 宏名#endif「判断宏是否未被定义」 ⭐高频▷ 含义和#ifdef完全相反→if not defined▷ 核心用途头文件保护头文件卫士单片机项目中100%必用解决「头文件重复包含导致的变量/函数重定义报错」// 写在所有头文件(.h)的最开头和结尾固定模板背下来#ifndef__LED_H__// 如果 __LED_H__ 未被定义#define__LED_H__// 就定义这个宏// 这里写头文件的内容变量声明、函数声明、宏定义sbit LEDP1^0;voidLED_Init(void);#endif// 结束条件编译3.#else条件分支配合#ifdef/#ifndef/#if使用实现「二选一」编译之前讲#ifdef时已经用过。4.#elif多条件分支 ⭐高频▷ 含义else if的预处理版本实现「多选一」编译搭配#if使用效果最好。▷ 单片机常用场景根据不同的硬件版本/功能等级编译不同代码。#defineDEVICE_VERSION3// 设备版本1低配版2标准版3高配版#ifDEVICE_VERSION1#defineLED_NUM1// 低配版1个LED#elifDEVICE_VERSION2#defineLED_NUM4// 标准版4个LED#else#defineLED_NUM8// 高配版8个LED#endif5.#if 表达式⭐⭐ 重中之重▷ 含义判断「表达式的值是否为真非0」满足则编译代码。▷ 区别你之前学的#ifdef#ifdef 宏名→ 只判断「宏有没有被定义」不管宏的值是多少#if 宏名→ 只判断「宏的值是不是非0」宏必须有值✅ 这是新手最容易混淆的点用一个经典例子彻底分清嵌入式调试开关必用#defineDEBUG0// 定义宏DEBUG值为0#ifdefDEBUG// ✔️ 结果真 → 编译因为DEBUG被定义了printf(调试信息1\r\n);#endif#ifDEBUG// ❌ 结果假 → 不编译因为DEBUG的值是0printf(调试信息2\r\n);#endif6.#elif defined(宏名)组合用法 ⭐高频#elif不能直接跟宏名需要配合defined()函数实现「多宏判断」比如#ifdefSTM32F103#includestm32f1xx.h#elifdefined(STM32F407)#includestm32f4xx.h#elifdefined(AT89C51)#includereg51.h#else#error未定义目标单片机型号#endif7.#error 提示文本强制报错指令 ⭐⭐ 超实用▷ 含义预处理阶段如果执行到这条指令编译器会直接报错并终止编译同时显示你写的「提示文本」。▷ 单片机用途做「强制校验」比如忘记定义硬件宏、参数配置错误时主动报错提醒避免程序烧录后出问题。8.#warning 提示文本警告指令▷ 含义和#error类似但只弹出警告不会终止编译适合做「友好提醒」比如#ifndefBAUD_RATE#warning未定义波特率默认使用9600#defineBAUD_RATE9600#endif✅ 第三类【文件包含指令】★★★★★ 最高频 没有之一#include 文件名或#include 文件名▷ 核心作用把「指定文件的全部内容」原封不动复制粘贴到当前代码的这个位置单片机中用来包含「头文件(.h)」。▷ 两种写法的区别嵌入式必知面试也常考#include xxx.h→ 包含系统头文件编译器自带的库文件比如#include reg51.h、#include stdio.h编译器会去「系统库路径」查找文件。#include xxx.h→ 包含自定义头文件自己写的.h文件比如#include led.h、#include uart.h编译器会先去「当前项目路径」查找找不到再去系统库路径查找。✅ 单片机开发建议所有自定义头文件用双引号系统头文件用尖括号规范且不易出错。✅ 第四类【行号/文件名控制】低频但实用这类指令平时用的少但在调试排错、生成日志时特别好用都是编译器内置的指令无需手动定义。1.#line 行号 [文件名]强制修改当前代码的「行号」和「文件名」预处理阶段生效比如#line100main_test.cprintf(当前行号%d\n,__LINE__);// 输出1002. 编译器内置预定义宏⭐ 调试神器这些宏是编译器自带的无需#define定义直接用单片机调试时用来打印「出错位置、编译时间」定位BUG效率翻倍重点记4个最常用的__LINE__当前代码的行号数字__FILE__当前代码的文件名字符串__DATE__程序编译的日期字符串格式月 日 年__TIME__程序编译的时间字符串格式时:分:秒✅ 单片机调试经典用法#ifdefDEBUG#defineLOG(fmt,...)printf([%s][%d] fmt,__FILE__,__LINE__,##__VA_ARGS__)#else#defineLOG(fmt,...)#endif// 使用LOG(数码管扫描初始化完成\r\n);LOG(当前温度%d℃\r\n,temp);✅ 第五类【编译器控制指令】★★★ 嵌入式高频#pragma#pragma 指令内容编译控制指令▷ 核心特点编译器专属指令不同编译器KEIL5、MDK、GCC的#pragma用法不同但功能强大是嵌入式开发中「优化代码、配置编译规则」的核心指令。▷ 单片机开发必学3个常用用法KEIL/MDK编译器#pragma once→ 替代「头文件卫士#ifndef」作用是让当前头文件只被包含一次写法更简洁✅ 二选一即可要么写#ifndef的头文件卫士要么写#pragma once效果一样。#pragma optimize0→ 关闭编译器的代码优化单片机调试时必用因为编译器优化后会删减一些「看似无用」的代码导致调试时变量值看不到、断点失效。#pragma anon_unions→ 允许使用匿名联合体单片机中定义寄存器结构体时常用节省代码量。✅ 第六类【冷门但有用的指令】低频了解即可这类指令在单片机开发中几乎用不到知道就行不用深究#pragma pack(n)设置结构体的内存对齐方式比如#pragma pack(1)表示按1字节对齐嵌入式中定义硬件寄存器时偶尔用。#undefine和#undef功能一致部分编译器的别名写法。#import和#include类似主要在C/Objective-C中使用C语言单片机不用。三、【重中之重】嵌入式开发 高频预处理指令「组合用法模板」直接套用所有指令中90%的开发场景都是几个指令组合使用我整理了单片机开发中最常用的5个模板都是现成的背下来直接写到你的项目里效率翻倍模板1头文件保护二选一必写// 方式1传统写法兼容性最好所有编译器都支持#ifndef__UART_H__#define__UART_H__// 头文件内容#endif// 方式2简洁写法KEIL/MDK推荐代码少#pragmaonce// 头文件内容模板2调试开关万能日志宏必写#defineDEBUG1// 1开调试0关调试#ifdefDEBUG#includestdio.h#defineLOG(fmt,...)printf([%s][%d] fmt,__FILE__,__LINE__,##__VA_ARGS__)#else#defineLOG(fmt,...)#endif模板3跨硬件平台适配51/STM32通用#defineTARGET_STM321// #define TARGET_51 1#ifdefTARGET_STM32#includestm32f1xx_hal.h#defineLED_GPIO_PORTGPIOA#defineLED_GPIO_PINGPIO_PIN_0#elifdefined(TARGET_51)#includereg51.h#defineLED_PINP1_0#else#error请定义目标单片机型号#endif模板4功能模块裁剪低配/高配版#defineHIGH_CONFIG1// 1高配版0低配版voidDevice_Init(void){// 基础功能必编译LED_Init();UART_Init();// 高配版专属功能条件编译#ifdefHIGH_CONFIGI2C_Init();SPI_Init();WIFI_Init();#endif}模板5宏定义引脚常量简化代码// 数码管引脚定义#defineSEG_PORTP0#defineBIT_PORTP2// 延时常量定义#defineSCAN_DELAY800#defineKEY_DELAY20// 功能开关#defineUSE_SMG1#defineUSE_KEY1#defineUSE_BEEP0四、【新手避坑】最容易搞错的「预处理指令误区」必看✅ 误区1预处理指令行尾加了分号→ 编译报错比如#define LED P1^0;❌正确是#define LED P1^0✔️。✅ 误区2把typedef当成预处理指令 →typedef是C语言的关键字不是预处理指令它是编译阶段执行的用来定义类型别名。✅ 误区3#ifdef判断宏的值 →#ifdef只判断「宏是否存在」不管值是多少判断值用#if。✅ 误区4头文件重复包含 → 一定要加#ifndef或#pragma once否则会报「重定义」错误。✅ 误区5带参宏不加括号 → 比如#define MUL(a,b) a*b调用MUL(12,3)会变成12*3正确写法是#define MUL(a,b) (a)*(b)✔️。五、【速查表】所有预处理指令 汇总按重要性排序方便查阅指令分类指令名称核心作用优先级宏定义#define定义宏/宏函数★★★★★宏定义#undef取消宏定义★★★★☆文件包含#include包含头文件★★★★★条件编译#ifdef判断宏已定义★★★★★条件编译#ifndef判断宏未定义★★★★★条件编译#endif结束条件编译★★★★★条件编译#else条件分支★★★★☆条件编译#elif多条件分支★★★★☆条件编译#if判断表达式真★★★★★编译控制#pragma编译器配置★★★☆☆报错提醒#error强制编译报错★★★☆☆警告提醒#warning编译警告★★☆☆☆行号控制#line修改行号文件名★☆☆☆☆总结C/C的预处理指令就这么多对单片机开发来说你只需要吃透「宏定义、条件编译、文件包含」这三类核心指令就能解决99%的开发场景剩下的指令用到时再查即可。以下是 C 语言基于 C99 / C11 / C17 标准中所有预处理指令的完整整理附带简要说明和示例。同时标注了 C23 标准新增的少数指令供参考。一、宏定义与取消指令 说明 示例#define 定义宏可带参数 #define PI 3.14 #define MAX(a,b) ((a)(b)?(a):(b))#undef 取消宏定义 #undef PI二、条件编译指令 说明 示例#if 条件为真时编译后续代码 #if defined(DEBUG)#ifdef 如果宏已定义则编译 #ifdef _WIN32#ifndef 如果宏未定义则编译 #ifndef HEADER_H#elif 否则如果与 #if 搭配 #elifSTDC_VERSION 201112L#else 否则 #else#endif 结束条件编译块 #endifdefined() 运算符在 #if / #elif 中判断宏是否已定义 #if defined(DEBUG) DEBUG1C23 新增#elifdef 和 #elifndef例如#ifdef A#elifdef B // 相当于 #elif defined(B)#endif三、文件包含指令 说明 示例#include … 在系统头文件路径中搜索并包含 #include stdio.h#include “…” 先在用户路径当前目录搜索再搜索系统路径 #include “myheader.h”四、错误与警告指令 说明 示例#error 产生编译错误并输出指定消息 #error “Unsupported platform”#warning 产生编译警告非标准但多数编译器支持 #warning “This feature is deprecated”五、行号控制指令 说明 示例#line 修改编译器认为的当前行号和文件名 #line 100 “newfile.c”六、编译器特定控制指令 说明 示例#pragma 提供编译器特定功能或行为 #pragma once防止头文件重复包含 #pragma pack(1)字节对齐七、空指令指令 说明 示例单独一个 # 空指令无任何效果有时用于提高可读性 # 单独一行注意单独一个 # 后不加任何预处理词是合法的预处理行C99 起。八、宏中的特殊运算符属于预处理器但并非独立指令运算符 说明 示例字符串化 将宏参数转换为字符串字面量 #define STR(x) #x → STR(hello) 展开为 “hello”粘贴/连接 连接两个记号形成新的预处理记号 #define CONCAT(a,b) a##b → CONCAT(var,1) 展开为 var1九、C23 新增的预处理指令简要C23 标准引入了几个新的预处理功能指令 说明#elifdef 等同 #elif defined(…)#elifndef 等同 #elif !defined(…)#embed 在编译时将二进制文件内容直接嵌入以字节序列形式常用组合示例头文件保护#ifndefMYHEADER_H#defineMYHEADER_H// 内容...#endif// MYHEADER_H
阅读完成 · 觉得有帮助?