网易首页 > 网易号 > 正文 申请入驻

972星标背后:这套C宏技巧让编译器"学会"了编程

0
分享至


GitHub上一个2015年的C语言仓库,最近被翻出972颗星。不是新框架,不是工具链,是一堆让人瞳孔地震的宏定义——用预处理指令实现条件判断、循环、甚至递归。

Paul Fultz II写的这份文档,标题朴素得像教科书附录:《C Preprocessor tricks, tips, and idioms》。但点进去的人,多半会经历三个阶段:这什么鬼→有点意思→我CPU烧了。

「##」运算符的暗面:连接符成了拦路虎

宏拼接(token pasting)的核心是`##`运算符。你想写个`IIF`宏实现布尔选择?直觉写法长这样:

#define IIF(cond) IIF_ ## cond

cond为0时展开成`IIF_0`,为1时展开成`IIF_1`,再分别定义这两个宏返回第二个或第一个参数。逻辑通顺,但有个致命陷阱。

问题出在`##`的副作用:它会抑制宏展开。假设你定义了`#define A() 1`,然后调用`IIF(A())(true, false)`。预期结果是`true`,实际展开却是`IIF_A()(true, false)`——`A()`根本没被求值,直接被当成字符串粘过去了。

Fultz的解法是多套一层壳。定义`CAT`宏做间接拼接:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)

#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

调用`CAT(IIF_, 1)`时,先展开参数变成`PRIMITIVE_CAT(IIF_, 1)`,这时候`##`才出手,得到`IIF_1`。多这一步,就绕过了抑制展开的坑。

这套`IIF`最终形态是:

#define IIF(c) PRIMITIVE_CAT(IIF_, c)

#define IIF_0(t, ...) __VA_ARGS__

#define IIF_1(t, ...) t

现在`IIF(A())(true, false)`能正确走到`true`了。这个模式被复用到一堆布尔运算宏里:`COMPL`取反、`BITAND`与运算,全是拼接+模式匹配的套路。

计数器的暴力美学:从0数到9,手写10个宏

C预处理器没有变量,但你可以假装有。`INC`和`DEC`宏用纯拼接实现加减——限制在0到9范围,因为再往上宏展开深度会炸。

实现方式毫无取巧:穷举。


#define INC(x) PRIMITIVE_CAT(INC_, x)

#define INC_0 1

#define INC_1 2

...一路写到`INC_9 9`(9加1溢出截断)。`DEC`同理,从`DEC_1 0`写到`DEC_9 8`。

看起来蠢,但这是图灵完备的地基。有了条件判断和加减,你就能控制循环次数。后面要讲的`REPEAT`宏,核心就是靠`INC`/`DEC`当计数器用。

探测技术:用变参数量子力学测"有没有"

检测一个值是否存在,或者检测参数是不是括号,这是预处理的黑魔法领域。Fultz用的技巧 exploit 了变参宏的一个特性:逗号分隔的参数数量会影响展开结果。

核心是两个宏:

#define CHECK_N(x, n, ...) n

#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)

#define PROBE(x) x, 1

调用`CHECK(PROBE(~))`时,`PROBE(~)`先展开成`~, 1`,所以整体变成`CHECK(~, 1)`。再进`CHECK_N(~, 1, 0,)`,取第二个参数,返回1。

但如果`PROBE`没展开(比如探测的目标不存在),`CHECK`直接拿到的是单个值,展开后变成`CHECK_N(那个值, 0,)`,返回0。

这个1和0就是布尔信号。文档里用这套实现了`IS_PAREN(x)`检测括号、`IS_EMPTY(x)`检测空参数,甚至能区分`0`和`()`——在C宏的世界里,这相当于类型系统了。

递归的幻觉:宏怎么调用自己

预处理器明令禁止直接递归:`#define A() A()`会导致无限展开,编译器会掐断。但间接递归可以——只要你每次换个名字。

Fultz的`REPEAT`宏用了一个"扫描-再扫描"技巧。定义两个互相指涉的宏,`REPEAT`和`REPEAT_`:

第一次扫描时,`REPEAT`展开成包含`REPEAT_`的形式;第二轮扫描时,`REPEAT_`再展开回`REPEAT`。中间插一个`EMPTY()`延迟宏,控制什么时候触发下一轮。

实际代码长这样(简化版):


#define REPEAT(count, macro, ...) \

WHEN(count) \

OBSTRUCT \

REPEAT_INDIRECT \

(DEC(count), macro, __VA_ARGS__) \

macro(DEC(count), __VA_ARGS__) \

`OBSTRUCT`和`WHEN`是条件控制宏,`REPEAT_INDIRECT`就是`REPEAT`的别名。每次递归计数器减1,到0时`WHEN`阻断展开。

这套机制能展开256层——受限于C标准规定的宏展开深度上限。对代码生成来说够用了:批量定义结构体字段、展开模板代码、甚至写有限的元编程。

从技巧到工程:Boost.PP和实际落地

Fultz写这份文档时,Boost.Preprocessor库已经存在多年。那个库用类似技巧实现了更完整的预处理元编程,但依赖关系重、编译慢、错误信息像天书。

这份文档的价值在于"最小可运行演示"。972个星标里,估计一半是收藏即学会,另一半是真拿去做代码生成了。嵌入式领域有人用它生成寄存器映射表,游戏引擎用它做反射系统的胶水层。

有个细节值得玩味:文档最后修订于2015年6月15日,C++14刚发布不久。那时候constexpr还没现在这么能打,模板元编程又编译慢,预处理宏是折中方案。现在C++20有了consteval,编译期计算能力暴涨,这类技巧的实用价值在下降。

但GitHub的星星数还在缓慢上涨。或许因为C语言本身没这些新特性,或许因为有人发现预处理宏的展开速度比模板快一个数量级,又或许纯粹是——看着`#define`实现图灵完备,有种看汇编写操作系统的颅内高潮。

文档的14次修订记录里,最早版本可以追溯到2013年。Fultz在issue区回复过一条评论:「这些技巧不是为了生产代码,是为了证明C预处理器不是纯文本替换器。」

你现在会把这套技巧用在什么场景?是宁愿忍受宏的调试地狱换编译速度,还是直接上现代C++的模板元编程?

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
5A 景区扛不住了!无序扩张后,年轻人已经彻底“祛魅”

5A 景区扛不住了!无序扩张后,年轻人已经彻底“祛魅”

爆角追踪
2026-05-04 13:09:11
遭世排第59爆冷!蒯曼1-0领先连输三局吞逆转 马琳场下抓耳挠腮

遭世排第59爆冷!蒯曼1-0领先连输三局吞逆转 马琳场下抓耳挠腮

颜小白的篮球梦
2026-05-04 20:46:51
“山西订婚强奸案”男子刑满释放回家 其母:刚回来状态有些紧张,以后要好好生活

“山西订婚强奸案”男子刑满释放回家 其母:刚回来状态有些紧张,以后要好好生活

红星新闻
2026-05-04 18:24:32
输掉抢七!立刻清洗!季后赛第一支解体球队

输掉抢七!立刻清洗!季后赛第一支解体球队

篮球教学论坛
2026-05-05 00:23:00
高端极地探险邮轮暴发罕见疫情已致3人死亡!汉坦病毒是否人传人?张文宏这样说

高端极地探险邮轮暴发罕见疫情已致3人死亡!汉坦病毒是否人传人?张文宏这样说

第一财经资讯
2026-05-04 16:36:20
我年过六十岁才恍然大悟:为什么大多数女人都对六十岁以上的男人敬而远之,甚至会主动回避,两个原因

我年过六十岁才恍然大悟:为什么大多数女人都对六十岁以上的男人敬而远之,甚至会主动回避,两个原因

心理观察局
2026-05-04 08:51:10
4月,房价跌幅继续扩大

4月,房价跌幅继续扩大

大川东山再起
2026-05-04 16:10:02
争议?19岁留洋红星庆祝李金羽下课!曾在铁人共事1年半+不受重用

争议?19岁留洋红星庆祝李金羽下课!曾在铁人共事1年半+不受重用

我爱英超
2026-05-04 20:30:35
为什么发达国家对中国都不友好?

为什么发达国家对中国都不友好?

新浪财经
2026-05-04 07:26:54
高市放话准备战斗,解放军深夜发出重磅信号,“大刀”已准备就绪

高市放话准备战斗,解放军深夜发出重磅信号,“大刀”已准备就绪

石江月
2026-05-02 19:11:37
兄弟俩联手创办苏宁,如今弟弟千亿资产清零,哥哥却走上另一条路

兄弟俩联手创办苏宁,如今弟弟千亿资产清零,哥哥却走上另一条路

兴史兴谈
2026-05-04 00:48:28
上科大39岁博导王晨辉救孩子去世,夫妻俩的实验室相邻,门上写着“科研有趣”,妻子的学生也会向他请教

上科大39岁博导王晨辉救孩子去世,夫妻俩的实验室相邻,门上写着“科研有趣”,妻子的学生也会向他请教

极目新闻
2026-05-04 22:27:34
伊朗媒体:两枚导弹击中穿越霍尔木兹海峡的美国军舰

伊朗媒体:两枚导弹击中穿越霍尔木兹海峡的美国军舰

新华社
2026-05-04 18:51:04
堪比努涅斯陨落!利物浦巨星断崖滑坡,名宿怒斥该彻底弃用

堪比努涅斯陨落!利物浦巨星断崖滑坡,名宿怒斥该彻底弃用

澜归序
2026-05-05 01:17:05
任正非未料:前华为员工执掌全球AI命脉

任正非未料:前华为员工执掌全球AI命脉

徐云流浪中国
2026-05-03 18:53:06
宣扬“美国优先”的美防长,他的夫人竟然也穿“中国货”?美国人“吵翻天”了

宣扬“美国优先”的美防长,他的夫人竟然也穿“中国货”?美国人“吵翻天”了

新民周刊
2026-05-04 16:33:44
缴物业费也要“政治正确”?别拿公职人员的“帽子”吓唬普通人

缴物业费也要“政治正确”?别拿公职人员的“帽子”吓唬普通人

迷世书童H9527
2026-05-04 19:24:17
手机正在打败中国的大学教育制度

手机正在打败中国的大学教育制度

回旋镖
2026-05-04 12:19:32
足坛最大实锤!凯塔亲口承认,当年睡了伊卡尔迪的老婆旺达?

足坛最大实锤!凯塔亲口承认,当年睡了伊卡尔迪的老婆旺达?

罗氏八卦
2026-05-04 18:05:03
阿联酋称油轮过霍尔木兹遭伊朗袭击

阿联酋称油轮过霍尔木兹遭伊朗袭击

界面新闻
2026-05-04 19:36:16
2026-05-05 02:15:00
算力游侠
算力游侠
游走在API与报错之间,用魔法(AI)打败魔法的非硬核玩家。
2093文章数 23关注度
往期回顾 全部

科技要闻

在中国市场搞「付费订阅」,豆包咋想的?

头条要闻

媒体:霍尔木兹海峡一声惊雷炸响 战争的引信已经点燃

头条要闻

媒体:霍尔木兹海峡一声惊雷炸响 战争的引信已经点燃

体育要闻

骑士破猛龙:加雷特·阿伦的活力

娱乐要闻

张敬轩还是站上了英皇25周年舞台

财经要闻

魔幻的韩国股市,父母给婴儿开户买股票

汽车要闻

同比大涨190% 方程豹4月销量29138台

态度原创

教育
游戏
家居
亲子
公开课

教育要闻

不写论文也能获博士学位?成都多所高校已出现“实践博士”

PS6新爆料太狠了:SSD性能翻倍 还能玩PS5游戏

家居要闻

灵动实用 生活艺术场

亲子要闻

高锌食物是个宝,孩子吃了记性好,胃口棒少生病

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版