#includeint f(int a, int b) { int add(int n) { return a+n; } return add(b); } int main(void) { printf("%d\n", f(1,2)); return 0; }
これが動くだけでもぎょっとしますが……
#includetypedef int int2int(int); int f(int a, int b) { int add(int n) { return n+a; } return g(b, add); } int g(int b, int2int* padd) { return (*padd)(b); } int main(void) { printf("%d\n", f(1,2)); return 0; }
これもうまく動いてしまいます。吐かせたアセンブリを読む限りでは、どうも f を呼び出した時のフレームポインタを %g2 レジスタに設定してから add を呼び出すラッパ関数をスタック上に動的に生成し、そのスタック上へのアドレスを関数アドレスとして渡している模様。SPARC だからこれでも動きますけど、データ領域ではコード実行できない x86 系ではどうやっているんでしょう……。
_ と、疑問に思って x86 上で動いている FreeBSD の gcc でコンパイルしてみたら、次のコードを吐きました。eax にはスタック上に確保したメモリ領域のアドレスが、ebp は現在のフレームのポインタが、edx には eax+10 と add のアドレスの差が入っています。
movb $185,(%eax) movl %ebp,1(%eax) movb $233,5(%eax) movl %edx,6(%eax)
185=0xb9 は mov cx,<imm> そして 233=0xe9 は相対 jmp です。本当にラッパを動的に生成している模様。
んむ〜。FreeBSD ではスタック領域に実行可能ビットが立っているのでしょうか〜。それとも、世の中そういうもの?
……って、ちょっとまった。なんでスタック上を指しているアドレスと、add の開始アドレスを単純に引き算なんてできるんですか?しかも相対 jmp で飛んでるのは何?なんか、そもそもメモリモデルを勘違いしている可能性大です。セレクタとか、そーゆー高級なものは存在しないのでしょうか……
_ すわ、closure か!?と期待してしまうのですが、残念ながらラッパをスタック上に取ってしまうため、関数ポインタを外に返すことはできません。あくまでネストした関数を定義したフレームが生き続けている範囲でのみ使用可能です。
ええっと、忘れてしまったのですが、このように宣言時の環境(変数名とそれ示すメモリセルの map)を関数が保持するのを deep binding っていうんでしたっけ。shallow binding や dynamic binding のどれがどれだか分からなくなりつつあります(^^;関数型言語の世界の用語ですね。
ちなみに、perl の無名関数が closure と呼ぶに足る機能を持っているのは有名な話です。fold こそないですが、map や sort でいろいろいじくっていると関数型的なプログラミングの世界を堪能できます。これが perl の懐の深さですね。
WindowsXP には標準で DEBUG コマンドがついてきてくれているので助かりました。Windows98 あたりではついていないんですよね〜。でも、DEBUG は 386 で拡張された 32bit 命令を理解できません……
unison を FreeBSD の ports から入れようとしたら、OCaml のコンパイルから始まりました……。しかも、入るのはまだ beta の unison-2.9.20 だし。さっきから延々とコンパイルしています……