自分のコードを出力するプログラム

面白いコードを見つけた。

自分のコードを出力するプログラム http://www.iba.k.u-tokyo.ac.jp/~yabuki/tip/lisp/self.html

このページは主眼はLispなんだけど、恥ずかしながら僕はLispは全然分からない。Lispを理解すると素晴らしい悟りが手に入るらしいので興味はあるんだけど、ここではパス。

このページにはこんなC言語のプログラムが載っていたのだ。

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

これは自分自身を出力するプログラムだ。このプログラムをコンパイルして実行すると、次の文字列を画面にプリントする。

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

つまり、自分自身のコードを出力する!うーむ、こりゃ面白いゾ。
僕はプログラムというのは「動く知識」だと思っている。その「知識」を生み出す「知識」というのは一つ上のメタな知識であって、全然レベルの違うもののハズだ。このメタな知識は「知恵」と言ってもいい。
しかしこのプログラムは自分自身を出力する。つまり「動く知識」を出力する。じゃあ「動く知恵」なのか?それは言い過ぎかもしれない。でも自分自身を出力するって不思議な循環があって神秘的な感じがする。このプログラムは作者が作り出した見事な知恵の輪だと思う。

野暮は承知で、なぜこのコードで自分自身が出力されるのか説明してみる。でもこれを読む前に、ぜひ自分で読み解いて欲しい。

まずmain関数は次のように書かれている。

main(a){...

このmainの引数aってなんだ?ってことで、main関数のプロトタイプを思い出してみる。

int main( int argc, char** argv ) ;

これを照らし合わせると引数aにはargc、つまりコマンド入力の引数の数がセットされることが分かる。そのaはこう使われている。

... printf(a,...)

・・・ん?printfの第一引数は書式指定の文字列じゃないの?
よく見ると、printfの3番目の引数にこう書かれている。

...printf(..., a="...", ...)...

そう、aには文字列が代入されているんだ。printf関数の実行よりもこの代入文のほうが先に評価される。だからprintfが呼び出された時点で既にaはこの文字列を指しているんだ。
ではaはどんな文字列なのか。

... a="main(a){printf(a,34,a=%c%s%c,34);}" ...

%c%s%c の部分に注目しよう。ここが書式指定だ。ご存知のように%cは文字を出力、%sは文字列を出力するための書式指定となる。つまりこのprintfには次のような引数が入力されているはずなんだ。

printf(a,文字1,文字列,文字2)

これと元のプログラムを照らし合わせると、次の値が入力されていることが分かる。

  • 文字1, 文字2 ... 34
  • 文字列 ... a="main(a){printf(a,34,a=%c%s%c,34);}"

おいおい、文字に34って数字が入っているじゃねーか。・・・と慌てることなかれ。落ち着いてASCIIコード34の文字を調べてみよう。それは " (ダブルクォーテーション)だ。
この辺で分かったことを整理して元のプログラムを書き換えてみよう。

main(a){
  printf(
    "main(a){printf(a,34,a=%c%s%c,34);}",  /* 書式指定文字列 */
    '"', "main(a){printf(a,34,a=%c%s%c,34);}", '"'
  );
}

こう書き換えればもう簡単だ。このプログラムの出力は次のようになる。

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

つまり元のソースとまったく同じ文字列だ。お見事!ぱちぱちぱち!

こんなプログラム、自分も作ってみたいなぁ。