國際C語言混亂代碼大賽作品分析!

來源:互聯網
上載者:User

今天QQ好友,咕嘟咕嘟給我發來了一段C代碼

#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);}

咋眼一看,我都暈了。哪來那麼多main函數。後來才知道,有兩個是被宏定義了的,早期的編譯器支援這樣的動詞。當然這是咕嘟咕嘟告訴我的。

以下是原文  轉載至http://blog.chinaunix.net/u1/43436/

這兩天閑來無事,重新讀《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 %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”,
我們就到此為至了吧!

經過分析,我們不由得不佩服這段代碼的作者,雖說這樣的代碼風格是絕對不提倡的,但能設計出這樣的代碼,不能不說是種“強悍”!

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.