先上代码。下面是一个完整的、可以编译、可以运行的 C 语言程序,它是一个从 9 倒数到 0 的倒计时器。请欣赏:
_.c1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <stdio.h>
#define _ int #define __ main #define ___ while #define _____ x #define ______ ; #define _______ () #define ________ { #define _________ } #define __________ ( #define ___________ ) #define ____________ = #define _____________ --> #define ______________ "%d\n", #define _______________ printf #define ________________ return
_ __ _______ ________ _ _____ ____________ 10 ______ ___ __________ _____ _____________ 0 ___________ ________ _______________ __________ ______________ _____ ___________ ______ _________ ________________ 0 ______ _________
|
如果你没有关掉这篇文章,说明你的好奇心还在。这个东西确实是 C 语言,而且用的不是乱码、不是 Unicode 花活,是从头到尾老老实实的 ASCII 下划线。
原理:一个宏就是一次填空
答案就藏在文件开头那串 #define 里——这不是代码,这是一张完形填空的答案表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define _ int #define __ main #define ___ while #define _____ x #define ______ ; #define _______ () #define ________ { #define _________ } #define __________ ( #define ___________ ) #define ____________ = #define _____________ --> #define ______________ "%d\n", #define _______________ printf #define ________________ return
|
C 语言允许你把任何东西定义成宏,关键字也不例外。于是:
_ 变成 int
__ 变成 main
___ 变成 while
______ 变成分号
________ 变成左花括号
- ……
一条下划线是 int,两条是 main,三条是 while——下划线的数量就是填空的题号。拿着答案表回头看那个程序,逐行”对答案”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| _ __ _______ → int main()
_ _____ ____________ 10 ______ → int x = 10 ;
___ __________ _____ _____________ 0 ___________ → while ( x --> 0 )
_______________ __________ ______________ _____ ___________ ______ → printf ( "%d\n", x ) ;
________________ 0 ______ → return 0 ;
|
它是一个倒计时器:从 9 打印到 0。真机运行,输出干干净净:
彩蛋:连 –> 梗都被做进了宏里
拆到 while 那一行,老 C 语言玩家会会心一笑:x --> 0——这不是什么”趋向运算符”,它是 x-- > 0 被空格美化后的样子,是 C 语言圈流传最广的视觉梗。而这个程序走得更远:把 --> 直接定义成了宏 _____________。也就是说在这个程序的世界观里,--> 真的是一种合法运算符——只要你觉得它是,宏就让它合法。
怎么复原?填空题的逆操作
看到这里你可能会问:如果我接手了别人这种代码,怎么还原?答案简单到离谱——预处理器的本职工作就是展开宏,让编译器替你”对答案”就行:
-E 表示只跑预处理阶段。打开输出的 a.i,所有宏已经被展开,下划线变回 int、main、while,一行不剩。当时我在文件夹里留了一个 复原代码.txt,全文就这一行命令。
这个系列的其他文章:尊嘟假嘟、会自杀的代码。系列缘起见抽象代码宣言。