void main(void) {printf(&unix["\021%six\012\0"], (unix)["have"]+"fun"-0x60);}
↑のコードは、コンパイルして実行すると、"unix"と表示されます。(∩´∀`)∩(´∀`)∩(´∀`∩)ワショーイ!!
このコードは、1987年のIOCCC優勝作品でもあり、WikipediaのIOCCC項目 で、一番最初に表示されているコードです。
また、こちらでは、より詳細にこのコードの解説をされている方がいるので、参考にして下さい^^;)
上記リンク先がなくなっていたので、私がコードの解説を書きます。
コードの解説に先立ち、もし上記コードをWindowsにて実行する場合は、以下の様にコード先頭に「#define unix 1」を書いてやる必要があります。
(∵ Unix環境では、unix が 1 と定義されている為)
#define unix 1
void main(void) {printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}
さて、ではこのコードを、VC++2005で実行してみます。
以下の様な警告がでますが、コンパイルは可能です。
「warning C4013: 関数 'printf' は定義されていません。int 型の値を返す外部関数と見なします。」
実行結果は以下の通り
それでは、コードを読み解いて行きます。
まず、コードに適当な改行、インデントを入れます。そして、警告がうるさいのでstdio.hをインクルードします。
1 : #include <stdio.h>
2 :
3 : #define unix 1
4 :
5 : void main(void)
6 : {
7 : printf(
8 : &unix["\021%six\012\0"],
9 : (unix)["have"] + "fun" - 0x60
10 : );
11 : }
そして、定義済のunixを1に置換します。
#include <stdio.h>
void main(void)
{
printf(
&1["\021%six\012\0"],
(1)["have"] + "fun" - 0x60
);
}
そして、i["hogehoge"] が "hogehoge"[i] と同義だという、C言語の摩訶不思議な交換法則を適応します。
1 : #include <stdio.h>
2 :
3 : void main(void)
4 : {
5 : printf(
6 : &"\021%six\012\0"[1],
7 : "have"[1] + "fun" - 0x60
8 : );
9 : }
さて、6行目はprintf()関数の第一パラメータであり、ここには char *が入ります。
"\021%six\012\0"という文字列の2文字目(配列は0-baseである事に注意)のアドレスを渡している為、
6行目全体は以下の文字列に置き換えられます。
"%six\012\0",
「%s」は、パラメータの文字列を差し込む位置、
「\012」は、8進数で「10」を表しており、これはアスキーコード表と照らし合わせると「\n(改行)」だと分かります。
「\0」は、おなじみのナル文字です。
これらを考慮し、最終的に6行目は、以下の様に置き換わります。
"%six\n",
つまり、冒頭のコード全体では、以下の様に置き換わります。
1 : #include <stdio.h>
2 :
3 : void main(void)
4 : {
5 : printf(
6 : "%six\n",
7 : "have"[1] + "fun" - 0x60
8 : );
9 : }
次に、7行目を解析します。
「"have"[1]」の部分は、「'a'」を指しており、「'a'」をアスキーコード表を元に10進数に直すと「97」です。
そして、16進数表記の「0x60」を10進数に直すと「96」です。
つまり7行目は以下の様に置き換えられます。
97 + "fun" - 96
これは
"fun" + 1
ということであり、"fun"の2文字目のアドレスを指す事になります。つまり "un"のアドレスです。
以上を整理すると、冒頭のコードは
#include <stdio.h>
void main(void)
{
printf(
"%six\n",
"un"
);
}
となり、めでたく「unix」と表示される理由が判明しました~~♪^^;)