国际C语言混乱代码大赛优胜作品详解之“A clock in one line”
原文链接:https://blog.csdn.net/herorenme/article/details/8864351
下面这段代码即为第19届 IOCCC(国际混乱C语言代码大赛)优胜作品:“A clock in one line”。
1
|
main(_){_^448&&main(-~_); putchar (--_%64?32|-~7[__TIME__-_/8%8][ ">'txiZ^(~z?" -48]>> ";;;====~$::199" [_*2&8|_/64]/(_&2?1:8)%8&1:10);} |
输出结果如下:(当前时间)
1
2
3
4
5
6
7
|
!! !!!!!! !! !!!!!! !! !!!!!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!!!!! !! !! !! !! !! !!!!!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!!!!! !! !! !! !!!!!! |
它究竟是如何做到的呢?下面为你解读:
首先,将这段代码格式化:
1
2
3
4
5
6
|
main(_) { _^448 && main(-~_); putchar (--_%64 ? 32 | -~7[__TIME__-_/8%8][ ">'txiZ^(~z?" -48] >> ";;;====~$::199" [_*2&8|_/64]/(_&2?1:8)%8&1 : 10); } |
引入变量:
1
2
3
4
5
6
7
8
9
10
11
|
main(int i) { if (i^448) main(-~i); if (--i % 64) { char a = -~7[__TIME__-i/8%8][ ">'txiZ^(~z?" -48]; char b = a >> ";;;====~$::199" [i*2&8|i/64]/(i&2?1:8)%8; putchar(32 | (b & 1)); } else { putchar(10); // newline } } |
根据补码的规则,可得-~i == i+1,所以:
1
2
3
4
5
6
7
8
9
10
11
12
|
main( int i) { if (i != 448) main(i+1); i--; if (i % 64 == 0) { putchar ( '\n' ); } else { char a = -~7[__TIME__-i/8%8][ ">'txiZ^(~z?" -48]; char b = a >> ";;;====~$::199" [i*2&8|i/64]/(i&2?1:8)%8; putchar (32 | (b & 1)); } } |
另外,因为C语言中a[b]等同于b[a],同时在运用 -~=1+ 规则,可得:
1
2
3
4
5
6
7
8
9
10
11
12
|
main( int i) { if (i != 448) main(i+1); i--; if (i % 64 == 0) { putchar ( '\n' ); } else { char a = ( ">'txiZ^(~z?" -48)[(__TIME__-i/8%8)[7]] + 1; char b = a >> ";;;====~$::199" [(i*2&8)|i/64]/(i&2?1:8)%8; putchar (32 | (b & 1)); } } |
将递归转换成循环,同时再做简化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// please don't pass any command-line arguments main() { int i; for (i=447; i>=0; i--) { if (i % 64 == 0) { putchar ( '\n' ); } else { char t = __TIME__[7 - i/8%8]; char a = ">'txiZ^(~z?" [t - 48] + 1; int shift = ";;;====~$::199" [(i*2&8) | (i/64)]; if ((i & 2) == 0) shift /= 8; shift = shift % 8; char b = a >> shift; putchar (32 | (b & 1)); } } } |
这样每次迭代会输出一个字符,每第64个字符会输出新的一行。
另外,它还使用数据表来设定输出形式,决定输出的是字符32(即字符空格)还是字符33(即字符 ! )。第一个表“>'txiZ^(~z?”是一组10位图,描述每个字符的外观;第二个表 “;;;====~$::199”的作用是,从位图中选择合适的位元来展示。
第二个表
我们先检查一下第二个表,“int shift = ";;;====~$::199"[(i*2&8) | (i/64)];”其中 i/64 是行数(从6到0);而 i*2&8 当且仅当i为4、5、6、7mod8时为8。
“if((i & 2) == 0) shift /= 8; shift = shift % 8”选择表的高8位(i%8=0、1、4、5)或者低8位(i=2、3、6、7)值。因此转换表最终看起来是这个样子:
1
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
28
29
|
row col val 6 6-7 0 6 4-5 0 6 2-3 5 6 0-1 7 5 6-7 1 5 4-5 7 5 2-3 5 5 0-1 7 4 6-7 1 4 4-5 7 4 2-3 5 4 0-1 7 3 6-7 1 3 4-5 6 3 2-3 5 3 0-1 7 2 6-7 2 2 4-5 7 2 2-3 3 2 0-1 7 1 6-7 2 1 4-5 7 1 2-3 3 1 0-1 7 0 6-7 4 0 4-5 4 0 2-3 3 0 0-1 7 |
或者显示为表格的形式:
1
2
3
4
5
6
7
|
00005577 11775577 11775577 11665577 22773377 22773377 44443377 |
注意:作者在表格的前两位使用了null terminator。(真狡猾!)
第一个表
__TIME__是预处理器定义的特殊的宏,它能扩展为一个字符串,内容为预处理器运行的时间,格式为“HH:MM:SS”,刚好占8个字符。注意:数字0-9的ASCII值为48-57,“:”的ASCII值为58。而每行输出64个字符,因此 __TIME__ 的每个字符有8个字符的空间。
“7 - i/8%8”是当前正在输出的 __TIME__ 的索引(其中“7-”是必须的,因为我们从 i 开始向下遍历)。因此 t 即 __TIME__ 要输出的字符。
a的值取决于t,对应关系如下:
1
2
3
4
5
6
7
8
9
10
11
|
0 00111111 1 00101000 2 01110101 3 01111001 4 01101010 5 01011011 6 01011111 7 00101001 8 01111111 9 01111011 : 01000000 |
每个数字都是一个位图,描述7段显示的字符。又因为是7位ASCII,所以高位会被清除,所以7位永远是空格,所以第二个表是这个样子:
1
2
3
4
5
6
7
|
000055 11 55 11 55 116655 22 33 22 33 444433 |
举个例子,4即01101010(1、3、5、6位显示),输出如下:
1
2
3
4
5
6
7
|
----!!-- !!--!!-- !!--!!-- !!!!!!-- ----!!-- ----!!-- ----!!-- |
理解了吗?现在我们再对输出做一些调整:
1
2
3
4
5
6
7
|
00 11 55 11 55 66 22 33 22 33 44 |
可以编码为“?;;?==? '::799\x07”。
出于美观考虑,我们把对64位做一些修改(因为输出仅使用低6位,所以不会受到影响),于是就变成了“?{{?}}?gg::799G”(注意:第8位并没有被使用,因此我们还可以做更多的衍生创作)。
现在代码就变成了:
1
|
main(_){_^448&&main(-~_); putchar (--_%64?32|-~7[__TIME__-_/8%8][ ">'txiZ^(~z?" -48]>> "?{{?}}?gg::799G" [_*2&8|_/64]/(_&2?1:8)%8&1:10);} |
输出结果如下:
1
2
3
4
5
6
7
|
!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! |
如预期的一样,看来我们的想法并没有错。