国际C语言混乱代码大赛优胜作品详解之“A clock in one line”

摘要:IOCCC,即国际混乱C语言代码大赛是一项著名的国际编程赛事迄今已举办22届,比赛的目的在于写出最富创意、最让人难以理解的C语言代码。本文解读了19届IOCCC优胜作品“A clock in one line”的工作原理,望对您有益。

下面这段代码即为第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
       !!              !!                              !!  
!!  !!              !!  !!  !!  !!              !!  !!  !!
!!  !!              !!  !!  !!  !!              !!  !!  !!
       !!      !!              !!      !!                  
!!  !!  !!          !!  !!      !!              !!  !!  !!
!!  !!  !!          !!  !!      !!              !!  !!  !!
       !!              !!                              !!

如预期的一样,看来我们的想法并没有错。

原文出自: StackOverflow

编辑分类

你可能感兴趣的:(C)