- ベストアンサー
本に載ってた難解なプログラム
とある本に載っていたもので main(){ printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);} (ヒント:#define unix 1) と、いうプログラムです。 実行してみてもやっぱりわかりませんでした。 解けないとどうなるという状況ではないので 暇なときに考えてやって下さい。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
Jizouと申します。 順に考えていきます。 main(){ printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);} (ヒント:#define unix 1) == Step 1: ========================================================= #defineマクロはソースコードのコンパイルが行われる前に実行されます から、ソースコード内の unix という文字列はすべて 1 という文字に 置き換えられます。置き換えたものが次のソースです。 main(){ printf(&1["\021%six\012\0"],(1)["have"]+"fun"-0x60);} == Step 2: ========================================================= 次に "\021%six\012\0" を解釈します。 文字列中の \0nn という表現は nn を8進数として解釈しますので、\021 は 10進数で 17( = 8 * 2 + 1)、16進数で 11 を示す値となります。同じよ うに \012 は 10進数で 10( = 8 * 1 + 2 )、16進数で 0a を示す値とな ります。\0 は 0(ゼロ)です。 \021 == 17 == \0x11 \012 == 10 == \0x0a ASCIIコード表で 17 が何を表すか分かりませんが、10 は「改行」を表しま すので '\n' と同じ意味です。(改行と '\n' が厳密に同じかどうかはちょ っと参考書をひっくり返さなければ分かりませんが、今回のケースでは同じ と考えても問題ないと思います) 文字列の最後はもともと終端コードとして '\0' が1個入っていますので、 このソースの場合には '\0' コードが2個入ることになります。 結局 "\021%six\012\0" は "\0x11%six\n" と同じ意味になります。 main(){ printf(&1["\0x11%six\n"],(1)["have"]+"fun"-0x60);} == Step 3: ========================================================= 次が最も難解な部分だと思いますが、&1["\0x11%six\n"] を解釈します。 これは良く使う形としては answer_text[3] という形と同じです。 C言語では文字列変数を char answer_text[100]; のように定義すると answer_text というラベルは宣言した文字列の先頭アドレスを表すラベルと して扱われます。 この文字列の4文字目を取り出す場合に answer_text[3] という書き方をし ますが、これは内部的には次のような形に変換される規則になっています。 answer_text[3] ===> *( answer_text + 3 ) answer_text というアドレスに 3 を加えて、そのアドレスから1文字取り 出すというわけです。 ところがこの変換式は answer_text の位置と 3 の位置に何を書いても上記 のように変換される規則になっています。ですので answer_text と 3 の位 置を入れ替えてもエラーなくコンパイルできてしまい、意味も同じになりま す。 3[answer_text] ===> *( 3 + answer_text ) ですので 1["\0x11%six\n"] はカギ括弧の中と外を入れ替えた "\0x11%six\n"[1] と同じ物なのです。この「文字列の後ろにインデックスを添える」という形 も普通は使いませんが、文字列変数も文字列も同じようなものだと思えばこ れは「その文字列の2文字目を示す」ものだということがお分かりいただけ ると思います。 1["\0x11%six\n"] ===> 文字列 "\0x11%six\n" の2文字目を示す。 今回のケースでは先頭にアンパサント(&)が付いていますので、さらに「そ のアドレスを示す」ということになります。つまり &1["\0x11%six\n"] ===> 文字列 "\0x11%six\n" の2文字目の先頭アドレスを示す。 ===> "%six\n" ということになります。(要するに先頭の1文字 '\0x11' を読み飛ばす) main(){ printf( "%six\n", (1)["have"]+"fun"-0x60);} == Step 4: ========================================================= お次は (1)["have"] です。 ここで (1) は 1 と同じですので実質的には 1["have"] と同じです。 これは上でやった内容と同じですので次のようになります。 (1)["have"] ===> 1["have"] ===> "have"[1] これは「"have" の2文字目」という意味になりますので、'a' ということ になります。 main(){ printf( "%six\n", 'a'+"fun"-0x60);} == Step 5: ========================================================= その次は 'a'+"fun"-0x60 です。 文字 'a' は16進数では 61 ですので C言語の表記では \0x61 となります。 さてその次の "fun" ですが、ソースコード中の文字列は式の中では「その文 字列の先頭アドレス」を示す値となります。 したがってこの式は次のように解釈されます。 'a'+"fun"-0x60 ===> 0x61 + "fun" - 0x61 ===> "fun" + 1 文字列アドレスに1を加えるということは、2文字目のアドレスを示すとい うことになります。 "fun" + 1 ===> "un" したがって最初のソースコードは次のソースコードと同じ意味になります。 main(){ printf( "%six\n", "un" );} == Step 6: ========================================================= ここで printf関数 の第1引数の中の "%s" は第2引数の文字列で置き換え て出力されますので、最終的には次の文字列が表示されることになるわけで す。 "unix"(+改行) 以上、お分かりいただけたでしょうか? まだ分からない点などありましたら、コメントに書き込んで下さい。 Jizou
その他の回答 (3)
配列の解釈と文字とコードの変換が頭に浮かぶかどうかが問われているわけですね。 "abc"[1] → *("abc" + 1) 1["abc"] → *(1 + "abc") このあたりの概念は馴染みずらい所かもしれない。
- ranx
- ベストアンサー率24% (357/1463)
unixは1を表すんですね。知りませんでした。 printf()が一つ呼ばれているだけですから、その引数の意味を示せばよいのだと思いますが、 まず、unix["\021%six\012\0"]ですが、アドレス1を先頭として、文字列"\021%six\012\0"の アドレス分だけプラスした文字のアドレス・・・ということは、とりも直さず 文字列"\021%six\012\0"の2文字目のアドレスということになります。(先頭はゼロですから。) で、\012は\nのことですから、この第一引数は"%six\n"と同じことになります。 第二引数は、同じ理屈で"have"の二文字目ですが、こちらはアドレスではありませんので、 'a'という値、つまり0x61になります。0x61に"fun"のアドレスを加え、0x61を引くということは つまり"fun"の2文字目のアドレスと同じことですから、第二引数は"un"です。 つまり、このプログラムは main(){printf"%six\n","un");} ということです。
そもそもどういう問題なのでしょう? 実行結果がどうなるかという問題ですか? それなら結果は unix と表示されますが。
補足
えーと、なんでも IOCCC(International Obfuscated C Code Competition:不明瞭なCプログラムの国際大会)の1987年に優勝した、ペル研のDavid Kornの作品 だそうで、作者は実行結果を予想できるか聞いていますが 私はなぜそうなるかが知りたいわけでして…