昨天看到這樣一例代碼,以前只是聽說過IOCCC的大名,實際一看代碼,果然兇殘……

大家都見過哪些讓你虎軀一震的代碼??

www.zhihu.com
圖標

方便起見,我直接將原答案內容粘貼到下面:

代碼如下:

#include <stdio.h>

char *a; main(int t,int _,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
"):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 wK:+}e#;dq#l q#+dK#!/+k#;q#r}eKK#}wr}eKK{nl]/#;#q#n){)#}w){){nl]/+#n;d}rw i;# ){nl]!/n{n#; r{#wr nc{nl]/#{l,+K {rw iK{;[{nl]/w#q#nwk 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,{}:
uwloca-O;m .vpbks,fxntdCeghiry"),a+1);}

執行結果:On the first day of Christmas my true love gave to mea partridge in a pear tree.On the second day of Christmas my true love gave to metwo turtle dovesand a partridge in a pear tree.On the third day of Christmas my true love gave to methree french hens, two turtle dovesand 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 dovesand a partridge in a pear tree.On the fifth day of Christmas my true love gave to mefive gold rings;four calling birds, three french hens, two turtle dovesand a partridge in a pear tree.On the sixth day of Christmas my true love gave to mesix 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 meseven swans a-swimming,six geese a-laying, five gold rings;four calling birds, three french hens, two turtle dovesand a partridge in a pear tree.On the eigth day of Christmas my true love gave to meeight 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 menine 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 dovesand a partridge in a pear tree.On the tenth day of Christmas my true love gave to meten 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 meeleven 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 dovesand a partridge in a pear tree.On the twelfth day of Christmas my true love gave to metwelve 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 dovesand a partridge in a pear tree.

代碼兩行,打出了一個英文歌詞……於是我代碼之魂爆發,想要解析一下~~

在我努力解讀了一下後發現,全是三目運算符,也就是個if嵌套,而最繞的,其實是main函數遞歸,裡面還用了31[a]這樣的寫法,甚至沒有else的單判斷,使用的是邏輯運算符短路的原理,於是我把這些統統篩選後,用if else重新改寫全部程序,並把兩個字元串宏起來,得到了一個還算清晰的代碼,運行結果是一致的:

#include <stdio.h>
#define LongStr1 "@n+,#/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/r :d*3,}{w+K wK:+}e#;dq#l q#+dK#!/+k#;q#r}eKK#}wr}eKK{nl]/#;#q#n){)#}w){){nl]/+#n;d}rw i;# ){nl]!/n{n#; r{#wr nc{nl]/#{l,+K {rw iK{;[{nl]/w#q#nwk 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 )# }+}##(!!/"
#define LongStr2 "!ek;dc i@bK(q)-[w]*%n+r3#l,{}:
uwloca-O;m .vpbks,fxntdCeghiry"

char *a;

main(int t,int _,char* a)
{
int res, tmp = 0;

if(t > 1)
{
if(t < 3)
{
tmp = main(-86, 0, a + 1);
tmp = main(-87, 1 - _, tmp + a);
main(-79, -13, a + tmp);
}
else
{
res = 1;
}

if(t < _)
{
main(t + 1, _, a);
}
else
{
res = 3;
}

if(main(-94, -27 + t, a) && t == 2)
{
if(_ < 13)
{
main(2, _ + 1, "%s %d %d
");
}
else
{
res = 9;
}
}
else
{
res = 16;
}
}
else
{
if(t < 0)
{
if(t < -72)
{
main(_, t, LongStr1);
}
else
{
if(t < -50)
{
if(_ == *a)
{
putchar(a[31]);
}
else
{
main(-65, _, a + 1);
}
}
else
{
if(*a == /)
{
tmp = 1;
}
main(t + tmp, _, a + 1);
}
}
}
else
{
if(t > 0)
{
main(2, 2, "%s");
}
else
{
if(*a != /)
{
tmp = main(-61, *a, LongStr2);
main(0, tmp, a + 1);
}
}
}
}
return res;
}

梳理後發現,t的值是關鍵,實際上t的值的有效區間就那麼幾個,根據t的值,能快速檢索程序運行到哪一個分支了。而根據main函數傳參的原理,第一次main函數進入的時候,t = 1,不難發現程序一開始第一次遞歸走的是main(2, 2, "%s");這一行,而第二次就進了最上面的三連遞歸那,執行的是main(-86, 0, a + 1);,第三次,執行了main(_, t, LongStr1);,這下就看到關鍵了,把LongStr1這個參數作為基準數據傳入了,而傳入的第一個參數變成了變數_,回去看一眼發現是0,那麼0走的位置是最下面的這個分支,只要*a不是/,那麼就會將*a和LongStr2傳入分支,而此時的a,是傳入的LongStr1,而-61決定了走了t<-50這個分支。也就是這段代碼:

if(t < -50)
{
if(_ == *a)
{
putchar(a[31]);
}
else
{
main(-65, _, a + 1);
}
}

這個分支很簡單,就是個查找,找不到,就走else,a+1後再找,只要第一個參數的值還在-72到-50這個範圍,就會無限進入這裡。這就是在LongStr2中找LongStr1中的第一個字元,找到了就輸出對應位置的第31個字元,也就是這裡用遞歸實現了一個strchr,然後找到後列印了對應位置後下標為31的位置的字元。

解讀到這裡,我其實已經破解了兩個字元串的加密方法,把第一個字元串中的每一個字元到第二個字元串中找,找到了輸出31個後的字元,其中/有特殊含義。好了,那我們這個時候就能寫一個簡單的代碼解讀一下這兩個字元串了:

#include <stdio.h>

#define LongStr1 "@n+,#/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/r :d*3,}{w+K wK:+}e#;dq#l q#+dK#!/+k#;q#r}eKK#}wr}eKK{nl]/#;#q#n){)#}w){){nl]/+#n;d}rw i;# ){nl]!/n{n#; r{#wr nc{nl]/#{l,+K {rw iK{;[{nl]/w#q#nwk 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 )# }+}##(!!/"
#define LongStr2 "!ek;dc i@bK(q)-[w]*%n+r3#l,{}:
uwloca-O;m .vpbks,fxntdCeghiry"

int main()
{
int i;

for (i = 0; LongStr1[i]; i++)
{
if(LongStr1[i] == /) ///的情況就原封不動輸出
{
putchar(/);
continue;
}
putchar(strchr(LongStr2, LongStr1[i])[31]); //在LongStr2中找LongStr1的每個字元,找到了就列印下標為31的。
}

return 0;
}

執行結果:

On the /first/second/third/fourth/fifth/sixth/seventh/eigth/ninth/tenth/eleventh/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.

/

這個結果很重要,後面要多次參照。其中,強行換行情況是
導致的。

到這裡,我們得到了作者加密字元串的方法,解出了作者的素材,接下來,我們就要來看看作者是如何把素材拼湊起來的了。我在源代碼的基礎上一塊一塊解釋。

if (*a != /)
{
tmp = main(-61, *a, LongStr2);
main(0, tmp, a + 1);
}

首先看這一段,這是列印前的最後關卡。根據上面的代碼就能知道,第一個參數傳0,就進入這裡。這就是一個連續列印的過程,只要*a的值不是字元/,那麼就會一直列印下去,直到字元/,那麼其實最後一句就是在不停列印。main(0, tmp, a + 1);這一句只要調用一次,就能列印出一個字元。他負責下次調用,上面那句負責列印出來,這就是這個if,如果用模塊的眼光看,這部分代碼就是列印用的。換句話說,其他地方只要出現了第一個參數是0的情況,就是列印一段內容,由於這是第一次列印,傳進來的直接是LongStr1全體,所以列印的是最開始"On the ",由此也可以猜測出,之後肯定會有傳入不同的地方,列印不同的位置。總之之後只要看到第一個參數是0,我已經不需要再想這是幹啥的了。

至此,一開始的三句遞歸調用中的第一句,已經搞明白了,下來看第二句main(-87, 1 - _,tmp + a);。

一看第一個參數是-87,基本能確定跟上一個一樣了,準備列印,但是第二個參數不一樣了,是「1-_」,那麼我大概明白,現在應該是準備玩第二個參數了。既然用了1-_這樣的形式,大體上也能猜到應該是有變化的。而我們通過分析列印結果就能發現,每一句中,"On the "後面的內容不一樣,是"first"、"second"、"third"……這樣的,那麼這個變數_應該有個地方在控制,但是現在先不管,我先看它當前的情況,目前的情況,_的值是2,也就是參數傳了一個-1,看看走到哪了,一看,還是main(_, t, LongStr1);,更剛才列印函數一樣,走到了剛剛分析過的列印分支,但是這次沒進if,而是進了else:

else
{
if(*a == /)
{
tmp = 1;
}
main(t + tmp, _, a + 1);
}

這一段,又是a+1,大概知道又是遞歸查找,而參數1為0的時候會進列印。所以,這個地方是統計/的次數。如果t進來的時候是-1,那麼只需要找到一個/就會進入列印,如果是-2,就需要找到兩個/才會進入列印,於是我們明白了,第一個參數在-50~0這個區間內並不是固定的意思,而是列印第幾個/後的字元串。如果第一個參數是-1,就列印第一個,之前分析的那個是0,那麼就是第0個,也就是從開頭打。OK,瞭解了這個,我明白了,第一次列印,是-1,看上面我的程序解析的字元串的結果,第一個/後是first,真相大白。這段代碼算是搞明白了。

上面的搞明白了,main(-79, -13, a+ tmp);也就明白了,列印第13個/後的內容,也就是" day of Christmas my true love gave to me
"。在這裡參數3無用,因為會在後面賦值,於是作者利用沒用的第三個參數,完成了遞歸調用,這就是源代碼中十分巧妙的地方,也就是我拆解的這部分,tmp完全沒意義,第三個參數隨便給個什麼都可以。解讀到這裡,其實八成的工作量已經結束了。

然後是這一段:

if(t < _)
{
main(t + 1, _, a);
}
else
{
res = 3;
}

這個時候我們已經不需要去看裡面的執行方式了,只在表層看就行了。t < _,第一次進來的時候由於總入口main(2, 2, "%s");,導致t和_都是2,第一次不走這裡,走了res=3,所以看不出什麼。再看接下來的一段:

if(main(-94, -27 + t, a) && t == 2)
{
if(_ < 13)
{
main(2, _ + 1, "%s %d %d
");
}
else
{
res = 9;
}
}
else
{
res = 16;
}

if括弧裏有一個調用,這個一定會執行,根據上面的解析,-94<-72,所以是列印意圖,-27+t是-25,列印第25個/後的內容,也就是"a partridge in a pear tree.

",這裡其實第一段已經打完了,也就是下面是段與段之前的銜接。

繼續看,返回值設定上不會是0,所以進入if,此時_ < 13成立,進入main(2, _ + 1, "%s %d %d
");,誒,這次不是列印了,這是幹啥?上去一看,哦,跟一開始的總入口差不多,給變數_加了1以後開始了下一段的列印。行了,剛才我就說了,肯定有一個地方會控制_的值,給他加一,行了,這個地方找到了。

第二段,上面三行沒有變化,只是因為_變成了3,所以傳入變成-2,列印變成了second。t < _這一段變了,因為此時2<3,進了main(t + 1, _, a);,這一句,t變成了3,遞歸後,前兩個if不進,反而進入了main(-94, -27 + t, a)這裡,此時t是3,-27+3=-24,所以要列印第24個/後的內容,也就是"two turtle doves
and ",然後遞歸結束,出來列印第25個……這下更清楚了,今後隨著_的值越來越大,這個遞歸的次數會越來越多。t是4的時候,就會列印23、24、25的內容,是5的時候,就會列印22、23、24、25的內容,所以就形成了一個逐漸變多的列印方式。一直到_的值到13,if(_ < 13)這句失效,程序就會全部結束。至此,全部解讀完畢,至於res的賦值,這個根本用不到,程序中,只要保證返回值不是0就能正常運行,返回值沒有意義的。於是最後,我完成了這個刪減了多餘東西的代碼,並進行了一些便於閱讀的調整,並附上我調試的時候的初版注釋:

#include <stdio.h>
#define LongStr1 "@n+,#/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/r :d*3,}{w+K wK:+}e#;dq#l q#+dK#!/+k#;q#r}eKK#}wr}eKK{nl]/#;#q#n){)#}w){){nl]/+#n;d}rw i;# ){nl]!/n{n#; r{#wr nc{nl]/#{l,+K {rw iK{;[{nl]/w#q#nwk 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 )# }+}##(!!/"
#define LongStr2 "!ek;dc i@bK(q)-[w]*%n+r3#l,{}:
uwloca-O;m .vpbks,fxntdCeghiry"

#define PRINT -100
#define SREARCH -50

int main(int t, int _, char* a)
{
int tmp = 0;

if (t > 1)
{
if (t < 3)
{
main(PRINT, 0, a); //列印開頭,也就是"On the "
main(PRINT, 1 - _, a); //列印中間部分的"first"、"second"等次數文字,變數_記錄了次數,(1-_)的絕對值就是從第幾個橫槓開始列印。_的範圍是從2開始逐漸增大。
main(PRINT, -13, a); //列印第一句的最後一段,也就是" day of Christmas my true love gave to me"
}

if (t < _) //而t在這一步結算的值開始為2,當_的值到3的時候進入這個部分
{
main(t + 1, _, a); //這個部分就是列印中間每次越來越多的那部分辭彙的入口,隨著_數量的增加,這個部分會被反覆調用
//方法很簡單,_的值每次都會自增1,讓t也增加,t一開始是2,增到_的大小為止就好。
//而_的值可以控制前面的/次數,t的值可以控制後面的斜線次數。
}

if (main(PRINT, -27 + t, a) && t == 2) //-27+t隨著t的值變化而變化,t-27的絕對值就是第幾個斜線,如果_是4,t就會從2變到4,列印從第23個到第25個斜線後的內容,是固定的"three french hens, "、"two turtle doves
and "和"a partridge in a pear tree.

"
//由於_每列印一次都會加1,那麼t的變化次數每次都會改變,每次都會比上次多列印一個。
{
if (_ < 13) //程序的總出口
{
main(2, _ + 1, a); //這裡就是通過_計算次數的地方,中間的參數就是計數器。
}
}
}
else
{
if (t < 0)
{
if (t == PRINT)
{
main(_, t, LongStr1); //列印單詞總入口
}
else
{
if (t == SREARCH)
{
if (_ == *a)
{
putchar(a[31]); //找到後列印31個後的字元
}
else
{
main(SREARCH, _, a + 1); //順次在LongStr2中查找下一個
}
}
else
{
if (*a == /)//統計每個斜槓的位置,碰到斜槓就給t+1,一直加到0就去列印。
{
tmp = 1;
}
main(t + tmp, _, a + 1); //列印單詞的入口,第一個參數就是控制什麼時候開始列印的關鍵,進來的時候是-3,就是列印第3個/,因為每一個/都會+1,一直加到0,就去列印了
}
}
}
else
{
if (t > 0)
{
main(2, 2, a); //程序的總入口
}
else
{
if (*a != /) //控制列印結束的標記,碰到下一個/就停止列印
{
tmp = main(SREARCH, *a, LongStr2); //當t==0的時候,進入列印入口
main(0, tmp, a + 1); //列印下一個字元,tmp其實是沒意義的。
}
}
}
}
return 1;
}

至此,終於是結束了這個解讀。

推薦閱讀:

查看原文 >>
相關文章