C言語でCurrying 5
ちょっと休んだけどまだ続くよ。
これまでのまとめ。
呼び出し規約
- cdecl
- 呼び出し側でスタック掃除
- stdcall
- 呼び出され側でスタック掃除
部分適用とは
関数を呼び出す際の引き数を、呼び出し時と事前との二つに分けて与える。問題になるのは
- 呼び出し側から見た引き数の個数と呼び出され側から見た引き数の個数が違う⇒どうやってスタックを掃除するか。
- 事前に与えた引き数をどう管理するか。
というところ。
不定長引き数への対応。
残念ながらstdcallで不定長引き数に対応するのは難しい。なので、cdeclでの部分適用を目指してみよう。
ダミー引き数を与える。
今手元にコンパイラがないので疑似コードで書くと
int add(int a, int b); int binded_add(int a, int b) { a = 123; jmp add; }
という感じ。スタック調整用のダミー変数を咬ませることにより、極めて自然に部分適用を達成できた。ダミー変数の不自然さを我慢できるならお勧め。
呼び出し規約を作る。
cdeclで呼び出された場合、cdeclとしてretしさえすれば問題はないはずだ。つまり、
ダミー呼び出し開始時のスタック | … | $1 | $2 | $R | |
---|---|---|---|---|---|
本体の呼び出し開始時のスタック | … | $1 | $2 | $3 | $R |
本体の呼び出し終了時のスタック | … | $1 | $2 | $3 | |
ダミー呼び出し終了時のスタック | … | $1 | $2 |
というように本体呼び出し終了からダミー呼び出し終了までの間でスタック位置を調整できれば良い。通常の呼び出し規約の範疇ではそのような機能はない。それなら作ろう。
とは言うものの、実際に部分適用する引き数の個数は限られている。一つしか部分適用しないと判っているのなら
ret $1
というようにret imm16で調整すれば良い。これくらいなら事前に生成することも十分に考慮できるだろう。