news 2026/4/26 17:33:56

编译前奏:预处理全面梳理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
编译前奏:预处理全面梳理


个人主页: 流年如夢

专栏: 《C语言》

文章目录

  • 一.预定义符号
  • 二.#define 定义常量
  • 三.#define 定义宏
  • 四.带有副作用的宏参数
  • 五.宏替换的规则
  • 六.宏和函数的对比
  • 七.#和##运算
    • 7.1# --> 字符串化
    • 7.2## --> 记号粘合
  • 八.命名约定
  • 九.#undef(即取消宏定义)
  • 十.命令行定义
  • 十一.条件编译
  • 十二.头文件的包含
    • 12.1两种包含方式
    • 12.2避免头文件重复包含
      • 12.2.1通用方法
      • 12.2.2个别编译器支持
  • 十三.其他预处理指令
  • 🎯总结
  • ⚠️易错点

Ladies and gentlemen,本篇文章讲的是预处理全过程、#define 宏、#与##、条件编译、头文件包含等核心知识点;全程高能,不容错过!!!
前言

预处理是C程序编译前的文本替换与处理阶段,不生成可执行代码,只对源文件做文本层面的替换、展开、删除、包含等工作;掌握预处理,能写出更简洁、更易维护、更少BUG 的代码

一.预定义符号

C语言内置预处理符号,可以直接使用:

  1. __FILE__--> 当前源文件名
  2. __LINE__--> 当前行号
  3. __DATE__--> 编译日期
  4. __TIME__--> 编译时间
  5. __STDC__--> 遵循 ANSI C 则为1

例如:

#include<stdio.h>intmain(){printf("当前文件:%s\n",__FILE__);printf("当前行号:%d\n",__LINE__);printf("编译日期:%s\n",__DATE__);printf("编译时间:%s\n",__TIME__);return0;}

运行结果

二.#define 定义常量

#defin的语法

#definenamestuff

例如:

#defineMAX1000#defineDEBUG_PRINTprintf("file:%s line:%d",__FILE__,__LINE__)

注意❗定义常量不要加分号,当宏替换后会多出分号

错误示例

#defineMAX1000;//<--这里多了分号

三.#define 定义宏

宏 = 带参数的文本替换

宏的语法

#definename(parameter-list)stuff

其中左括号必须紧跟宏名,不能有空格

错误示例

#defineSQUARE(x)x*x

🧐分析:调用SQUARE(5+1)会替换为5+1*5+1=11,结果并非36,而是11

正确示例

#defineSQUARE(x)((x)*(x))

四.带有副作用的宏参数

副作用 --> 参数被多次求值

例如:

#defineMAX(a,b)((a)>(b)?(a):(b))
intx=5,y=8;intz=MAX(x++,y++);

替换后变成👉:

z=((x++)>(y++)?(x++):(y++));

运行结果

五.宏替换的规则

  1. 先检查参数,替换其中已定义的宏
  2. 接着替换文本插入原位置
  3. 然后再次扫描,继续替换(宏不能递归

👉需要注意的是字符串常量内的内容不被替换宏不能递归定义

六.宏和函数的对比

对比函数
代码长度每次调用都插入,代码变长只一份,调用跳转
执行速度快(无需调用)稍慢
优先级问题易出错,必须加括号无问题
副作用参数危险,多次求值安全,只求值一次
参数类型无类型限制类型严格
调试不可调试可逐行调试
递归不可递归可递归

👉宏的优势在于可以接收类型做参数,但函数做不到:

#defineMALLOC(num,type)(type*)malloc((num)*sizeof(type))

七.#和##运算

7.1# --> 字符串化

#宏参数变成字符串

#definePRINT(n)printf("the value of "#n" is %d\n",n)

其中调用PRINT(a)替换为:

printf("the value of ""a"" is %d\n",a);

7.2## --> 记号粘合

就是把两个符号合成一个标识符

#defineGENERIC_MAX(type)\type type##_max(type x,type y)\{returnx>y?x:y;}

再使用:

GENERIC_MAX(int)GENERIC_MAX(float)

最后生成👉:

intint_max(intx,inty);floatfloat_max(floatx,floaty);

八.命名约定

这个简单,宏名全大写,函数名不全大写(这是为了便于区分,避免误用)

九.#undef(即取消宏定义)

取消宏定义

#undefMAX

用于重新定义宏

十.命令行定义

编译时指定宏,不修改代码(在gcc编译器里)

gcc-D ARRAY_SIZE=10test.c

在程序中(vs2022)可以直接使用

intarr[ARRAY_SIZE];

十一.条件编译

条件编译即按条件决定是否编译某段代码,常用于调试、跨平台

常用的指令

#if//-->条件为真则编译#elif//-->否则如果#else//-->否则#endif//-->结束#ifdefSYMBOL//-->已定义则编译#ifndefSYMBOL//-->未定义则编译

例如(调试日志开关):

#define__DEBUG__1//这里1为真#if__DEBUG__printf("debug info\n");#endif

十二.头文件的包含

12.1两种包含方式

  1. #include "filename"--> 先找当前目录,再找标准库
  2. #include <filename>--> 直接找标准库路径

12.2避免头文件重复包含

12.2.1通用方法

#ifndef__TEST_H__#define__TEST_H__//内容#endif

12.2.2个别编译器支持

#pragmaonce

十三.其他预处理指令

#error--> 编译时报错
#pragma--> 向编译器发送指令
#line--> 重置行号

🎯总结

  1. 预处理是纯文本处理,不涉及语法、语义
  2. 宏是高效替换,必须全程加括号
  3. 宏参数有副作用(++--)会引发严重问题
  4. #字符串化,##记号粘合
  5. 条件编译用于跨平台、调试、裁剪代码
  6. 头文件必须加保护,避免重复包含
  7. 宏比函数快,但更危险、难调

⚠️易错点

  1. 宏定义不加括号,优先级出错
  2. 宏参数带副作用++--
  3. 常量定义末尾多写分号
  4. 分不清#include <>""的区别
  5. 忘记头文件保护,导致重复包含
  6. 用feof判断循环结束,逻辑错误

👀 关注我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞鼓励原创,让优质内容被更多人看见
⭐ 收藏收好核心知识点与实战技巧,需要时随时查阅
💬 评论分享你的疑问或踩坑经历,一起交流避坑、共同进步

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 17:33:08

打造你的专属Galgame数字图书馆:TouchGal社区平台完全指南

打造你的专属Galgame数字图书馆&#xff1a;TouchGal社区平台完全指南 【免费下载链接】kun-touchgal-next TouchGAL是立足于分享快乐的一站式Galgame文化社区, 为Gal爱好者提供一片净土! 项目地址: https://gitcode.com/gh_mirrors/ku/kun-touchgal-next 在二次元游戏的…

作者头像 李华
网站建设 2026/4/26 17:33:08

专业IDE选择:VS Code与PyCharm的LLM开发插件与配置秘籍

006、专业IDE选择:VS Code与PyCharm的LLM开发插件与配置秘籍 昨天深夜调试一个LangChain调用流程,明明本地测试正常,一上测试环境就报错。对着终端里大段的JSON输出翻了半小时,才发现是某个环节的prompt模板里多了个不起眼的空格。这种时候才深刻体会到——工具选不对,de…

作者头像 李华
网站建设 2026/4/26 17:30:06

从ResNet到DenseNet:图解Element-wise Add和Concat如何塑造了现代CNN架构

从ResNet到DenseNet&#xff1a;图解Element-wise Add和Concat如何塑造了现代CNN架构 在深度学习的演进历程中&#xff0c;神经网络架构设计经历了从简单堆叠到精心设计的转变。2015年&#xff0c;ResNet通过残差连接&#xff08;Residual Connection&#xff09;彻底改变了卷积…

作者头像 李华
网站建设 2026/4/26 17:28:25

Poco:更安全易用的AI智能体框架,OpenClaw的现代化替代方案

1. 项目概述&#xff1a;从OpenClaw到Poco的进化之路如果你和我一样&#xff0c;在过去一年里深度体验过各种AI智能体框架&#xff0c;那么对OpenClaw这个名字一定不会陌生。它作为早期基于Claude Code的智能体实现&#xff0c;确实让我们看到了AI自主执行复杂任务的潜力。但说…

作者头像 李华
网站建设 2026/4/26 17:28:22

Go语言的runtime.GC垃圾回收器算法演进与未来发展方向

Go语言自2009年诞生以来&#xff0c;其垃圾回收&#xff08;GC&#xff09;机制一直是性能优化的核心。从最初的标记-清除算法到如今的三色并发标记&#xff0c;runtime.GC的演进不仅提升了性能&#xff0c;更体现了Go团队对高并发场景的深刻理解。本文将探讨GC算法的演进历程&…

作者头像 李华