C/C++程序员的花样编程

【导读】:说到 C/C++ 代码技巧,也许会有童鞋说 #define true false,这是属于 C/C++ 程序员离职前恶搞之类的抖机灵。即便想,也不能干。别忘了有这样一句编程名言:「在编写代码的时候,你要经常想着,那个最终维护你代码的人可能将是一个有暴力倾向的疯子,并且他还知道你住在哪里。」

本文整理了两位知乎网友对《你见过哪些令你瞠目结舌的C/C++代码技巧?》的回复分享。

二、Shiky Chang 回答,1225 顶

如果说「瞠目结舌」的话,IOCCC 上随便拿一篇获奖代码出来就足以让人下巴落地了。

The International Obfuscated C Code Contest

一个比较经典的例子是 1988 年得奖的代码,这个程序直接估算字符面积求圆周率,可读性算是比较友好的:

•westley.c•

#define _ F-->00||-F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

注:这段程序实际上是 1989 年修正过的,由于 88 年原来程序代码没有考虑到 ANSI C 的编译标准,导致在处理例如

#define _ -i
-_

的时候,老旧的 K&R 框架和 ANSI C 结果不一样:K&R 是直接的

--i

而 ANSI C 编译结果实际上等同于

-(-i)

因此之前的程序现在运行的话出来的结果是 0.250,而不是 3.141。修正过的程序就没有这个问题了。

又比如 13 年有个只有一行的程序,可以判断从 Franklin Pierce 往后的 31 位美国总统是民主党还是共和党,这个就有点不知所云了:

•cable1.c•

main(int riguing,char**acters){puts(1[acters-~!(*(int*)1[acters]%4796%275%riguing)]);}

使用方法:

make cable1
 
./cable1 obama republican democrat
./cable1 bush republican democrat

总统名要小写,republican 和 democrat 顺序不能颠倒。

@chua zier 提醒,历史上的确有重名的美国总统,除了 Johnson 之外,还有 Theodore Roosevelt / Franklin D. Roosevelt,程序原作者注明用

./cable1 roosevelt republican democrat

表示 Theodore Roosevelt,而用

./cable1 fdr republican democrat

表示 Franklin D. Roosevelt。

这一行代码做了这么多事:首先查询输入的总统的名字,然后在一个 look-up table 里面找出对应的政治阵营,再输出出来。问题在于这 31 位总统名字存放在哪里?而这个 look-up table 又存放在哪里?

有趣的是,IOCCC 的评委还提到,你甚至可以用这个程序检测一些 IT 大佬的 Mac / PC 阵营:

    ./cable1 Cooper Mac PC
    ./cable1 Noll Mac PC
    ./cable1 Broukhis Mac PC
    ./cable1 Jobs Mac PC
    ./cable1 Gates Mac PC
    ./cable1 Ballmer Mac PC

难道这个程序暴露了 Ballmer 离开微软的真相?

最近几届比赛的代码为了增加混乱程度,代码越来越长,可读性也越来越差(不过话说回来,让可读性变得越来越差其实原本就是这个比赛的第一宗旨吧),不少代码甚至本身就是个 ASCII artwork……比如 11 年有一只阿卡林:

•akari.c•

为了保持美观我就直接上图了。源代码见此:ioccc.org/2011/akari/ak

–––––––––– !!! 前方阿卡林军团高能预警 !!! ––––––––––

这个阿卡林程序实际上是一个图像 down-sampler,可以接受符合条件的 PGM / PPM 灰度图像或者 LF 换行(不支持 CR-LF)的 ASCII art 为输入,然后转换输出一个处理后的图像 / ASCII art。不过这个阿卡林最逆天的地方在于,它可以用自身的源代码文本作为输入,输出生成另一个可以编译运行的程序的代码!而且把这个生成的程序文本继续作为输入做进一步 down-sample,又可以生成一段可以编译的代码,如此反复,可以套多达4层!详细的食用方法如下:

make akari
./akari akari.c akari2.c

然后生成的阿卡林·2号是这个样子的:

•akari2.c•

看不清?请摘下眼镜或者退远了看。注意,阿卡林·2号也是可以编译运行的,她的功能是把输入的 ACSII 文本的每个字符中间插入空格以及每行之间插入空行,生成一段“疏松”了的文本。我们用阿卡林·2号自己做实验品

make akari2.c
./akari2  akari2fat.txt

成功了!生成了一只阿卡林·2号·舒松

•akari2fat.txt•

阿卡林·2号还能干别的,她支持一个 rot13 参数:

./akari2 rot13  akari2fat.txt

生成的是经过 ROT13 仿射变换的文本,我们称之为阿卡林·2号·舒松·加蜜吧!

但是还没完……如果我们把原版阿卡林放进去再来一层呢?

./akari < akari.c | ./akari > akari3.c

于是阿卡林·3号诞生:

•akari3.c•

      wm_aoi(n)
  /*ity,,[2*/{}char*y=
 (")M{lpduKtjsa(v""YY"
 "*yuruyuri") ;main(/*
/",U/  R)U*  Y0U= ="/\
*/){puts    (y+ 17/*
 "NR{I="       ){/=*
   =*         */);/*
   **/{      ;;}}

可怜的阿卡林·3号,由于“马赛克”(down-sample)次数太多,摘了眼镜也只能模糊看到一点点……我们来问问阿卡林·3号对于诞生的感受吧:

make akari3
./akari3

于是她回答:

yuruyuri

居然会说ゆるゆり!

最后,我们尝试生产一下阿卡林·4号

./akari < akari.c | ./akari | ./akari > akari4.c

•akari4.c•

   main
(){puts("Y"
"U RU YU "\
"RI"   )/*
 */   ;}

顺利生产!虽然内容已经直截了当了,不过我们还是采访一下她吧:

make akari4
./akari4

她的答复是:

YU RU YU RI

至此,阿卡林军团全部诞生!

不得不佩服作者构建代码的精妙程度。他的个人主页在这里:uguu… (这位作者其实已经是这比赛的常客了,先后一共拿过 6 次不同的奖项。)

@马琦明 提醒,我又把上面这位作者的另一个作品搬出来了,13 年的 Most Catty——炮姐程序。这程序的代码长这个样子:

•misaka.c•

源代码:ioccc.org/2013/misaka/m

对的,当你看到原来是这个“御坂”的时候,你就知道,我们要开始造(kè)人(lóng)了……

make misaka

这个御坂的作用是把输入的 ASCII 横向连接起来。首先连接两个自己试试:

./misaka misaka.c misaka.c > misaka2.c

“把两个御坂输入一个御坂,会生成什么?”“两个御坂。”

•misaka2.c•

听起来很不可思议但是在这位作者的构建下完全不出意外地,上面这个御坂-2 居然也是可以编译运行的:

make misaka2.c

御坂-2 的功能是把输入的 ASCII 纵向连接起来。那我们就试着纵向连接两个御坂:

./misaka2 misaka.c misaka.c > misaka3.c

于是御坂-3 诞生了:

•misaka3.c•

我们来运行一下这个御坂-3。你此时脑中的景象可能是这样的:

但是你错了,御坂-3 会给你造出来更加精神污染的那只 long cat:

make misaka3
./misaka3

没错就是这只喵:

这里其实有 Unix 的 cat 指令的梗……如果之前你在执行御坂-2 的时候,用了更多的御坂作为输入,例如 4 个:

./misaka2 misaka.c misaka.c misaka.c misaka.c > misaka4.c
make misaka4.c
./misaka4

那么御坂-4 会给你造一只更长的 looooong cat:

按作者的意思,你可以最多叠加 31 个御坂来生成一只 looo….ooong cat(具体上限由编译器的 sizeof(int) 决定)。

13 年还有浙大教授侯启明写的 ray tracer 程序,虽然代码本身存在争议是否符合比赛规则,例如为避免长度超限制而使用了一些压缩方法、程序是个死循环。如果这段程序可读性不是这么恶心的话其实还是非常值得钻研的,里面用到了很多有趣的数据结构和着色体系。

食用方法也很简单,把程序挂在那儿跑一晚上,强制退出,就可以看结果了。由于是无穷尽的递归,程序跑的时间越长,图像就越精致。详细的说明和源文件还是参考官网吧:

Previous IOCCC Winners with spoilers

这里有个示例图。

侯老师还有另外三个作品上榜,一个是极其酷炫的 syntax highlightener,还有一个(源代码本身就是 GUI 的)科学计算器,后面这个已经有人 @paintsnow 回答过了。最新一个是上个月刚刚公布的新一届获奖作品 MD5 without integers,但是这个的源码还没有公布,估计要等到明年了。

你也许感兴趣的:

发表回复

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