國際C語言混亂代碼大賽----1988獲獎作品賞析

原始代碼:

#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 %d\n"):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 %d\n";
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 %d\n";
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 : I) : J) : (K ? (L ? M : (N ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? O
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q)
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R))
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R)) :
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R)) : S)
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R)) : (S ?
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R)) : (S ? T
A ? (B ? C : D1) , (D2 : E : F1) , (F2 ? (G ? H : I) : J) : (K ? (L ? M : (N ? (O ? P : Q) : R)) : (S ? T :
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));
爲了便於觀察,我們簡寫出最終識別結果:
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                   // !0<t 爲假 ,故然後執行 (K(LM(N(OPQ)R))(STU))
K                   // t<0 爲假 ,故然後執行 (STU)
S                   // 0<t   爲真,故執行 T
T                   // main(2,2,"%s")遞歸調用自身,表達式的值爲函數返回值,現在無法確定,
//進入main(2,2,"%s") 將此處標記爲一號位置
A                   // 1<2 爲真,故執行 (B?C:D1),(D2?E:F1),(F2?(G?H:I):J)) 這是一個逗號表達式,執行次序是從左向右依次求值,
                  // 最終取值卻是最後一個逗號表達式的值,即 (F2?(G?H:I):J))的值,雖然前面的表達式的值並不被引用,但仍然要執行
//故先進入 (B?C:D1) 將此處標記爲二號位置
B                   //   t<3 爲真,故執行
C                   //   main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a)),首先,這裏再次遞歸調用自身,表達式的值爲函數返回值,現在無法確定,將此處標記爲三號位置
                  //其次,在遞歸調用的同時,傳遞參數分別又調用兩次自身,而C語言的函數參數是從右向左入堆棧,所以先調用 main(-86,0,a+1)
//下面進入main(-86,0,a+1) 將此處標記爲四號位置
A                   //   1<-86爲假
K                   //   -86<0爲真
L                   // -86<-72爲真
M                   // 調用 main(_,t,STRB),即main(0,-86,STRB)
//進入 main(0,-86,STRB)   將此處標記爲五號位置
A                   // 1<0爲假
K                   // 0<0爲假
S                   // 0<0爲假
U                   // *a此時爲'@',故爲假,那麼繼續判斷 main(0,main(-61,*a,STRD),a+1),此過程又將進入 main(-61,*a,STRD)
//進入main(-61,*a,STRD),即使 main(-61,'n',STRD) 注意參數的運算順序,先調用了a+1,然後才調用此函數,經過此函數,a已經指向STRD了
A                   // 1<-61爲假
K                   //   -61<0爲真
L                   // -61<-72爲假,那麼繼續 (N(OPQ)R)
N                   // -61<-50 爲真,那麼繼續 (OPQ)
O                   // 變量'_'此時的值爲'n',而*a爲'!',故爲假
Q                   //   調用main(-65,_,a+1)       
//進入main(-65,_,a+1),即main(-65,'@','e') ,
A                   // 1<-65爲假
K                   // -65<0爲真
L                   // -65<-72爲假
N                   // -65<-50爲真
O                   // 爲假
Q                   //調用 main(-65,_,a+1)
//進入main(-65,_,a+1),即main(-65,'@','k'),這裏可以看到,每調用一次Q,a指針向後移動一次,直到a指向STRD中的@
//中間過程不用再重複了,我們直接考慮當a指向STRD中的@時的情況:
A                   //
K                   //
L                   //
N                   //
O                   // 此時終於爲真,那麼調用P
P                   // putchar(31[a]);這是什麼意思呢?其實數組在編譯的過程是轉換爲指針運算的,31[a]也就相當於a[31] ,而a此時所指的
                      //位置是STRD中的'@',相當於a[0],那麼,a[31]就是 'O',到這裏終於該從層層的遞歸中返回了,那麼,返回值是什麼呢?
                      //返回值就是這裏最後的表達式P的值,也就是putchar()函數的返回值,它返回什麼呢?返回輸出的字符的ASCII碼,也就是79
//一層層返回,每次返回值都是79,那麼這個79最終到達哪裏了呢? 對,返回到五號位置的U語句了
//那我們繼續 main(0,-86,STRB) 的U語句:
U                   //   main(0,main(-61,*a,STRD),a+1)即可變成   main(0,79,'n'),很不幸,我們需要繼續判斷這個函數的返回值,這裏標記爲五號位置
//進入 main(0,79,'n')
A                   // 1<0爲假
K                   // 0<0爲假
S                   // 0<0 爲假
U                   // 非常不幸,又是U ! 這個時候參數a是多少呢?對了,a 的值其實在上面已經計算並且入棧了,所以還是'n'
                      // 還是不等於'/' ,那麼,還要重複上面的過程!其實我們已經可以找到點規律了:
                      // 這次進入main(-61,*a,STRD)函數又會輸出什麼呢? 可以看出,傳遞進去的參數a又重新指向STRD的首地址了,
                      // 這次是一直循環到等a指向 'n'的時候,再次後移31位並輸出,我們可以算出,這次應該輸出 'n',並返回'n'的ASCII碼110
其實,分析到這裏我們就可以大概瞭解整個代碼的運行過程了,可以看到,程序中輸出語句只有一個putchar(),然而,用./a.out | wc統計得到,該程序
一共輸出字符數是2358個!!!那麼可以想到,要輸出完這些字符,函數至少遞歸了2358次!(其實不止這個數)考慮到分析該程序的目的“just for fun”,
我們就到此爲至了吧!

運行結果:

On the first day of Christmas my true love gave to me
a partridge in a pear tree.

On the second day of Christmas my true love gave to me
two turtle doves
and a partridge in a pear tree.

On the third day of Christmas my true love gave to me
three french hens, two turtle doves
and a partridge in a pear tree.

On the fourth day of Christmas my true love gave to me
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the fifth day of Christmas my true love gave to me
five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the sixth day of Christmas my true love gave to me
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the seventh day of Christmas my true love gave to me
seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the eigth day of Christmas my true love gave to me
eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the ninth day of Christmas my true love gave to me
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the tenth day of Christmas my true love gave to me
ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the eleventh day of Christmas my true love gave to me
eleven pipers piping, ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

On the twelfth day of Christmas my true love gave to me
twelve drummers drumming, eleven pipers piping, ten lords a-leaping,
nine ladies dancing, eight maids a-milking, seven swans a-swimming,
six geese a-laying, five gold rings;
four calling birds, three french hens, two turtle doves
and a partridge in a pear tree.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章