引続きアセンブリ言語を記述するやり方を書きます.
実は関数の中でいきなり全部のレジスタを自由に使える訳ではありません. Visual C++やgccではeax, ecx, edxは自由に使えますが, ebx, esi, edi, ebp(ともちろんespも)は関数を出る時に値が変わってはいけないことになっています. 従ってそれらのレジスタを使う時は入った時の値を保存しなければなりません. 例えば前回のsum()では次のように書いても構いません (これは説明のためであってこんな周りくどい書き方はしませんが).
proc sum push esi ; esiをスタックに保存 push edi ; ediをスタックに保存 mov esi, [esp+4+4*2] ; esiに一番目の引数を代入 mov edi, [esp+8+4*2] ; ediに二番目の引数を代入 add esi, edi ; esiにediを足す mov eax, esi ; 返り値であるeaxに代入 pop edi ; ediを元に戻す pop esi ; esiを元に戻す ret
push esiでスタック領域に保存しておき, pop esiでesiの値を元に戻します.
注意することが二つあります. 一つ目は
push edi push esi
としたのなら
pop esi pop edi
と逆順でpopしないといけないことです. "スタック"つまりFILO(First In, Last Out)なので先に入ったものは後から出てくるからです. ちゃんとペアになっていないとretとしたときに訳の分からない番値へ戻ることになり, ハングアップか強制終了になってしまいます.
二つ目はpushした回数 * 4だけespの値が減ってしまっているので, パラメータが格納されている場所を参照するところも変更しないといけないことです. 今は2回pushしたので
mov esi, [esp+4 +4*2]
としておかないと変な値が入ってしまいます.
#非常に間違え易いところです.
スタックで説明したときと比べて
[esp+12+4*2] □□□□ (何が入っているか分からない) [esp+ 8+4*2] □□□□ (bの値) [esp+ 4+4*2] □□□□ (aの値) [esp+ 0+4*2] □□□□ (関数が呼ばれた場所) [esp- 4+4*2] □□□□ (esiの値) [esp- 8+4*2] □□□□ (ediの値)
と変化しているわけです.
pushの回数に応じて引数の参照先が変わってしまうという煩わしさを回避する方法もあります.
proc sum push ebp mov ebp, esp push esi push edi mov esi, [ebp+8] mov edi, [ebp+12] add esi, edi mov eax, esi pop edi pop esi pop ebp ret
このように常に
push ebp
mov ebp,esp
...
pop ebp
と書いておけばn番目の変数は[ebp+(n+1)*4]に入っていることになります.
何も考えないでVC++でインラインアセンブラを使うとこうなっています.
#VC++自体は自在にebpを駆使しますが.
ただ, ebpレジスタを使えなくなりますので私は好きではありません.
#あるいはNASMのマクロを駆使すれば自動化もできなくはないです.