OKWAVEのAI「あい」が美容・健康の悩みに最適な回答をご提案!
-PR-
解決
済み

本に載ってた難解なプログラム

  • 暇なときにでも
  • 質問No.165236
  • 閲覧数331
  • ありがとう数1
  • 気になる数0
  • 回答数4
  • コメント数0

お礼率 31% (6/19)

とある本に載っていたもので

main(){ printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}
(ヒント:#define unix 1)

と、いうプログラムです。
実行してみてもやっぱりわかりませんでした。

解けないとどうなるという状況ではないので
暇なときに考えてやって下さい。
通報する
  • 回答数4
  • 気になる
    質問をブックマークします。
    マイページでまとめて確認できます。

質問者が選んだベストアンサー

  • 回答No.4
レベル5

ベストアンサー率 80% (4/5)

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
-PR-
-PR-

その他の回答 (全3件)

  • 回答No.1

そもそもどういう問題なのでしょう? 実行結果がどうなるかという問題ですか? それなら結果は unix と表示されますが。 ...続きを読む
そもそもどういう問題なのでしょう?
実行結果がどうなるかという問題ですか?
それなら結果は
unix
と表示されますが。
補足コメント
minimum

お礼率 31% (6/19)

えーと、なんでも

IOCCC(International Obfuscated C Code Competition:不明瞭なCプログラムの国際大会)の1987年に優勝した、ペル研のDavid Kornの作品

だそうで、作者は実行結果を予想できるか聞いていますが
私はなぜそうなるかが知りたいわけでして…
投稿日時 - 2001-11-08 16:25:13
  • 回答No.2
レベル13

ベストアンサー率 24% (357/1463)

unixは1を表すんですね。知りませんでした。 printf()が一つ呼ばれているだけですから、その引数の意味を示せばよいのだと思いますが、 まず、unix["\021%six\012\0"]ですが、アドレス1を先頭として、文字列"\021%six\012\0"の アドレス分だけプラスした文字のアドレス・・・ということは、とりも直さず 文字列"\0 ...続きを読む
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");}
ということです。
  • 回答No.3

配列の解釈と文字とコードの変換が頭に浮かぶかどうかが問われているわけですね。 "abc"[1] → *("abc" + 1) 1["abc"] → *(1 + "abc") このあたりの概念は馴染みずらい所かもしれない。
配列の解釈と文字とコードの変換が頭に浮かぶかどうかが問われているわけですね。

"abc"[1] → *("abc" + 1)
1["abc"] → *(1 + "abc")

このあたりの概念は馴染みずらい所かもしれない。
このQ&Aで解決しましたか?
関連するQ&A
-PR-
-PR-
こんな書き方もあるよ!この情報は知ってる?あなたの知識を教えて!
このQ&Aにはまだコメントがありません。
あなたの思ったこと、知っていることをここにコメントしてみましょう。

その他の関連するQ&A、テーマをキーワードで探す

キーワードでQ&A、テーマを検索する
-PR-
-PR-
-PR-

特集


いま みんなが気になるQ&A

関連するQ&A

-PR-

ピックアップ

-PR-
ページ先頭へ