一段代码的分析
M67的那篇文章在这:http://www.matrix67.com/blog/archives/1598
很久之前就转到自己博客上来了,在这里,当时也没细看,当然也没看明白。
今天仔细分析了下,终于明白怎么回事了。
先用字符串“I love you to death! You are the cutest and sweetest girl I've ever met.”生成程序为:
#include <stdio.h> main(t ,_,a) char*a;{return t<1?main(*a,a[-t],"=a-1kj3gnm:q\ ebh_cf*<r.d>i^+?,()[?qzyrjuvcdefg\ h,!kbpolwxs'.t main(")&&a[-t]&&main (t-1,_,a):t/2?_==*a?putchar(32[a]) :_%115<36||main(t,_,a+1):main( 0,t,")?r<g:?1<3?+<#?#m:}(\ w+b_?1<}3?tt(yk:?+|b:\ ?n3+:>+?([m?>.::+\ :>+?e)kr?)ig{\ :?y:~g:k?\ ,:+^") ;}
是一个很漂亮的心形,用gcc编译执行输出:
i love you to death! you are the cutest and sweetest girl i've ever met.#
上面的代码很漂亮,但不是很容易看明白,我们把程序代码简单整理一下:
main(int t, int _, char *a) { return t<1 ? main(*a,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main(")&&a[-t]&&main(t-1,_,a): t/2 ? _==*a ? putchar(32[a]): _%115<36||main(t,_,a+1): main(0,t,")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#") ; }
最开始的程序用了c的old style声明函数,没有类型的参数默认为整型。
分析程序的执行过程:
开始执行:t=1,_和a不确定,因为t为参数个数所以恒等于1。
因此接下来会执行第二个条件语句的后面的分支:
main(0,t,")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#");
递归调用之后:
t=0, _=1, a=")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#"
由于t<1,所以会执行:
main(*a,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main(")&&a[-t]&&main(t-1,0,a)
首先是递归调用:
t=')'=41, _=')', a="=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main("
由于t>0且t/2!=0所以会执行:
_==*a ? putchar(32[a]): _%115<36||main(t,_,a+1);
如果_!=*a,则递归调用,同时t和_不变,a加1,直到_等于*a为止,然后输出a之后的第32个字符。
接着递归一层层返回,到最开始执行:
main(*a,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main(")
的结束,因为没有指定返回类型,从运行结果上看每次都返回真,于之后就执行:
a[-t]&&main(t-1,0,a)
首先判断a[-t]是否为0,否则递归调用main(t-1,0,a),也就是转到下一个字符。
很明显这个字符串就是一个密码表。我们注意到在生成程序的时候,只有最后那个字符串在变,也就是生成的密码,生成的程序实际上就是用来解密的。
为了更清楚的理解过程,我们再对程序做进一步的变形:
我们发现
main(0,0,")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#");
这句只会执行一次,就是开始进入的时候,因为之后之后t要么<=0,要么>1。
main(int t, int _, char *a) { if (t==1) {//start main(0,t,")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#"); } if (t<=0) { main(*a,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main("); if (a[-t]) main(t-1,_,a); }else { if (_==*a) { putchar(a[32]); }else if(_%115>=36) { main(t, _, a+1); } } }
由于第一个递归只调用一次,所以可以单独拿出来,再又发现_参数只在
main(*a,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main(");
这一句中有实际意义,其它地方可以随意指定。
于是再把代码变形:
void show(int t, int _, char *a) { if (t<=0) { show(1,a[-t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main("); if (a[-t]) { show(t-1,_,a); } }else { if (_==*a) { putchar(a[32]); }else if(_%115>=36) { show(t, _, a+1); } } } int main(int argc, char *argv[]) { show(0,0,")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#"); return 0; }
我们最后再把递归也消除了:
void disp(int _, char *a) { char *p=a; while (_!=*p) { if (_%115<36)return; p++; } putchar(p[32]); } void show2(char *a) { int t=0; while (a[t]) { disp(a[t],"=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main("); t++; } } int main(int argc, char *argv[]) { show2(")?r<g:?1{<3?+<?vm:(+b_?1<v3?(k:?!+uvb:?nt3+:>{+?([m?>.::+:>+#?e)kr?)ig:?:!g:k?,:+^z#"); return 0; }
到此程序流程就一清二楚了,最后再来看看这程序怎么生成的,前面已经说过,只有密码是在变的,所以生成程序中主要就是生成密码。
这是生成程序的js代码:http://www.matrix67.com/data/scripts/ioccc.js
里面定义了三个字符数组:
var m=new Array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ',',','.','\'','!','?'); var c=new Array('(','f','n','m',':','q','e','b',')','j','c','r',',','[','<','*','a','k','>','+','3','g','.','d','1','-','?','h','^','i','_','='); var r=new Array("s","t","u","v","w","x","y","z","{","|","}","~","!","#");
其中m和c大小为32,m是输入字符串中可以出现的,其它被忽略。m可以说是明文字符,c就是与之对应的密文。r中的字符可以出现在密文中,但是没有与之对应的明文,这就是为什么生成的程序中有个_%115<36的判断,就是忽略这部分字符。程序生成程序其实就是按顺序生成密文,其中随机插入一些r中的干扰字符。
C是根据m和生成程序中的
“=a-1kj3gnm:qebh_cf*<r.d>i^+?,()[?qzyrjuvcdefgh,!kbpolwxs'.t main("
这个密码表字符串推导出来的,从第一个=开始数,第32个就是与之对应的m中字符,依次数32个就完成了c字符串。这样看来,其实密码表和密文字符串都是可以动态生成的。
结束。。
2011年10月06日 06:24
分析得不错。
看了40分钟,终于明白了。:)
ps:输出的结尾怎么有个#号?
2011年10月10日 19:11
'#'是putchar()输出到终端的。原因我也不太清楚。。
2011年10月11日 00:59
错了,这个是shell的问题,有些shell会强制换行,如果最后没有输出换行的话,会强制换行,然后显示高亮的#号。
比如bash配置不强制换行的话,就不会显示#,或者你最后输出一个'\n'的话,也不会显示#。