#include <stdio.h>
main(t,_,a)char *a;{return!0<t?t<3?
main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s %d %dn"):9:16:t<0?t<-72?
main(_,t,"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/")
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:nuwloca-O;m .vpbks,fxntdCeghiry"),a+1);}
咋眼一看,我都暈了。哪來那麼多main函數。後來才知道,有兩個是被宏定義了的,早期的編譯器支持這樣的動詞。當然這是咕嘟咕嘟告訴我的。
這兩天閒來無事,重新讀《C專家編程》一書,再次看到裡面提到的國際C語言混亂代碼大賽,突然對那些代碼來了興致,決定分析一番,而書中那個BASIC解釋器程序,我認為從原理上說太復雜了(起碼要理解如何解釋BASIC語言吧?),更不用說從混亂的代碼分析了!我記得以前在網上看過另一個大賽作品,程序打印一首英文歌曲的歌詞,想想原理應該比這個簡單,所以GOOGLE了一下,找到它了:
#include <stdio.h>
main(t,_,a)char *a;{return!0<t?t<3?
main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s %d %dn"):9:16:t<0?t<-72?
main(_,t,"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/")
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:nuwloca-O;m .vpbks,fxntdCeghiry"),a+1);}
怎麼樣?乍一看蠻嚇人的吧?我們今天就分析它啦!可能有人要說,分析這種程序根本沒有意義,那我冒昧借用Linus的一句名言“Just for fun”!只當是沒事了自我娛樂而已吧!:-)
如果你有一個支持語法高亮的編輯器,立刻可以看到,程序中有一大段字符串,我們知道,被雙引號括起來的字符串裡的內容是不會解釋成代碼語句的(轉義字符就算了吧),那我們第一步就是把這些字符串提取出來,現在代碼看起來是這樣的:
#include <stdio.h>
main(t,_,a)
char *a;
{
char * STRA="%s %d %dn";
char * STRB="@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/";
char * STRC="%s";
char * STRD="!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:nuwloca-O;m .vpbks,fxntdCeghiry";
return
!0<t?t<3?main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a))
:1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,STRA):9:16:t<0?t<-72?main(_,t,STRB)
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,STRC):*a=='/'||main(0,main(-61,*a,STRD),a+1);
}
怎麼樣?代碼是不是一下子清晰了很多?好,我們繼續觀察:在抽取了字符串後,我們發現程序的實際語句只有一個return(如果覺得不可思議你可以搜索一下";" C語言一個分號對應一條語句嘛,可以發現,除了字符串中的內容,的確只有一個分號)。然後我們又發現,語句裡有很多"?"和":",這是什麼?對了,是三目運算符,而C語言中三目運算符的優先級基本上是最低的(除了賦值和逗號運算符之外,再次搜索代碼部分,發現根本沒有賦值語句,而逗號運算符只有兩個),我們把'?'':'','當作分隔符,任意兩個分隔符直接的內容都用大寫字母代替,那麼程序可以變成這樣:
#include <stdio.h>
#define A !0<t
#define B t<3
#define C main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a))
#define D1 1
#define D2 t<_
#define E main(t+1,_,a)
#define F1 3
#define F2 main(-94,-27+t,a)&&t==2
#define G _<13
#define H main(2,_+1,STRA)
#define I 9
#define J 16
#define K t<0
#define L t<-72
#define M main(_,t,STRB)
#define N t<-50
#define O _==*a
#define P putchar(31[a])
#define Q main(-65,_,a+1)
#define R main((*a=='/')+t,_,a+1)
#define S 0<t
#define T main(2,2,STRC)
#define U *a=='/'||main(0,main(-61,*a,STRD),a+1)
main(t,_,a)
char *a;
{
char * STRA="%s %d %dn";
char * STRB="@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/";
char * STRC="%s";
char * STRD="!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:nuwloca-O;m .vpbks,fxntdCeghiry";
return
A ? B ? C : D1 , D2 ? E : F1 , F2 ? G ? H : I : J : K ? L ? M : N ? O ? P : Q : R : S ? T : U ;
}
替換的到底正確與否呢?編譯,通過,運行,和原來相同!說明替換成功!
下面的重點就是分析A ? B ? C : D1 , D2 ? E : F1 , F2 ? G ? H : I : J : K ? L ? M : N ? O ? P : Q : R : S ? T : U ;這個語句。
我們需要復習一下運算符的優先級和結合性的知識:
不同運算符之間按優先級識別,相同優先級的運算符直接按結合性識別,而"?:"運算符的結合性是從右向左的,那麼我們可以模擬編譯器讀入此語句的方式得到:
A
A ?
A ? B
A ? (B ?
A ? (B ? C
A ? (B ? C :
A ? (B ? C : D1)
A ? (B ? C : D1) ,
A ? (B ? C : D1) , D2
A ? (B ? C : D1) , (D2 :
A ? (B ? C : D1) , (D2 : E
A ? (B ? C : D1) , (D2 : E :
A ? (B ? C : D1) , (D2 : E : F1)
A ? (B ? C : D1) , (D2 : E : F1) ,
A ? (B ? C : D1) , (D2 : E : F1) , F2
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? G
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I)
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J)
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : K
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? L
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : N)
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H