混淆C代码大赛2006年。请解释sykes2.c
这个C程序如何工作?
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
它编译原样(在gcc 4.6.3
上testing)。 它打印编译时的时间。 在我的系统上:
!! !!!!!! !! !!!!!! !! !!!!!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!!!!! !! !! !! !! !! !!!!!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!!!!! !! !! !! !!!!!!
资料来源: sykes2 – 一行中的时钟 , sykes2作者提示
一些提示:没有编译警告每个默认。 用-Wall
编译,会发出以下警告:
sykes2.c:1:1: warning: return type defaults to 'int' [-Wreturn-type] sykes2.c: In function 'main': sykes2.c:1:14: warning: value computed is not used [-Wunused-value] sykes2.c:1:1: warning: implicit declaration of function 'putchar' [-Wimplicit-function-declaration] sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of '|' [-Wparentheses] sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of '|' [-Wparentheses] sykes2.c:1:1: warning: control reaches end of non-void function [-Wreturn-type]
让我们去混淆它吧。
缩进:
main(_) { _^448 && main(-~_); putchar(--_%64 ? 32 | -~7[__TIME__-_/8%8][">'txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1 : 10); }
引入variables来解决这个混乱:
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
因为二补。 所以我们有
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)); } }
现在,请注意a[b]
与b[a]
相同 ,并再次应用-~ == 1+
更改:
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)); } }
把recursion转换成一个循环,偷偷摸摸地简化一下:
// 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(a !
)。 第一个表( ">'txiZ^(~z?"
)是一组描述每个字符外观的10位图,第二个表( ";;;====~$::199"
)select适当的位来显示位图。
第二张桌子
我们先来看第二个表, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
。 i/64
是行号(6到0),如果i
是4,5,6或7 mod 8, i*2&8
是8。
if((i & 2) == 0) shift /= 8; shift = shift % 8
if((i & 2) == 0) shift /= 8; shift = shift % 8
select表格值的高八进制数字(对于i%8
= 0,1,4,5)或低八进制数字(对于i%8
= 2,3,6,7)。 换class表最终看起来像这样:
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
或以表格forms
00005577 11775577 11775577 11665577 22773377 22773377 44443377
请注意,作者对前两个表项使用空终止符(偷偷摸摸!)。
这是在七段显示器后devise的,其中7
为空白。 所以,第一个表中的条目必须定义点亮的段。
第一张桌子
__TIME__
是预处理器定义的特殊macros。 它以"HH:MM:SS"
的forms扩展为包含预处理器运行时间的string常量。 注意它包含正好8个字符。 请注意,0-9具有ASCII值48到57,并且:
具有ASCII值58.输出是每行64个字符,因此每个字符的__TIME__
留下8个字符。
7 - i/8%8
因此是目前正在输出的__TIME__
的索引(因为我们向下迭代,所以需要7-
)。 所以, t
是输出__TIME__
的字符。
a
取决于inputt
,以二进制结束等同于以下内容:
0 00111111 1 00101000 2 01110101 3 01111001 4 01101010 5 01011011 6 01011111 7 00101001 8 01111111 9 01111011 : 01000000
每个数字都是描述在我们的七段显示中点亮的段的位图 。 由于字符全部是7位ASCII,所以高位总是被清除。 因此,段表中的7
总是打印为空白。 第二个表格看起来像这样,7s为空格:
000055 11 55 11 55 116655 22 33 22 33 444433
所以,例如, 4
是01101010
(第1,3,5和6位),打印为
----!!-- !!--!!-- !!--!!-- !!!!!!-- ----!!-- ----!!-- ----!!--
为了显示我们真的了解代码,让我们用这个表调整一下输出:
00 11 55 11 55 66 22 33 22 33 44
这被编码为"?;;?==? '::799\x07"
。 出于艺术目的,我们将64个字符添加到一些字符(因为只使用低6位,这不会影响输出)。 这给了"?{{?}}?gg::799G"
(注意,第8个字符是未使用的,所以我们可以根据自己的需要进行设置)。 把我们的新表放在原始代码中:
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
我们得到
!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!
就像我们预料的那样。 它不像原来的那么扎实,这就解释了为什么作者select使用他所做的表格。
让我们格式化,以便阅读:
main(_){ _^448&&main(-~_); putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10); }
所以,运行它没有参数,_(argc通常)是1
。 main()
会recursion地调用它自己,传递-(~_)
的结果(负数不是_
负数),所以真的会去448次recursion(仅仅在这里_^448 == 0
)。
448/64 == 7
,它将打印7个64字符宽的行(外部三元条件,和448/64 == 7
)。 所以让我们把它改写成一个更清洁的东西:
main(int argc) { if (argc^448) main(-(~argc)); if (argc % 64) { putchar((32|-(~7[__TIME__-argc/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[argc*2&8|argc/64]/(argc&2?1:8)%8&1)); } else putchar('\n'); }
现在, 32
是十进制的ASCII空间。 它可以打印一个空格或“!” (33是'!',因此' &1
'在最后)。 让我们把重点放在中间的blob:
-(~(7[__TIME__-argc/8%8][">'txiZ^(~z?"-48]) >> (";;;====~$::199"[argc*2&8|argc/64]) / (argc&2?1:8) % 8
另一个海报说, __TIME__
是程序的编译时间,是一个string,所以有一些string运算,以及利用数组下标是双向的:a [b]与b [a ]为字符数组。
7[__TIME__ - (argc/8)%8]
这将select__TIME__
中的前8个字符之一。 然后索引到[">'txiZ^(~z?"-48]
(0-9个字符是48-57十进制)。这个string中的字符必须被select为它们的ASCII值。通过expression式继续进行操作,以根据angular色的字形内的位置来打印“或”!
添加到其他解决scheme中, -~x
等于x+1
因为(0xffffffff-x)
相当于(0xffffffff-x)
。 这等于2s补码中的(-1-x)
,所以-~x
是-(-1-x) = x+1
。
我尽可能地去模糊模algorithm,并删除了迭代
int pixelX, line, digit ; for(line=6; line >= 0; line--){ for (digit =0; digit<8; digit++){ for(pixelX=7;pixelX > 0; pixelX--){ putchar(' '| 1 + ">'txiZ^(~z?"["12:34:56"[digit]-'0'] >> (";;;====~$::199"[pixel*2 & 8 | line] / (pixelX&2 ? 1 : 8) ) % 8 & 1); } } putchar('\n'); }
再扩展一下:
int pixelX, line, digit, shift; char shiftChar; for(line=6; line >= 0; line--){ for (digit =0; digit<8; digit++){ for(pixelX=7;pixelX >= 0; pixelX--){ shiftChar = ";;;====~$::199"[pixelX*2 & 8 | line]; if (pixelX & 2) shift = shiftChar & 7; else shift = shiftChar >> 3; putchar(' '| (">'txiZ^(~z?"["12:34:56"[digit]-'0'] + 1) >> shift & 1 ); } } putchar('\n'); }