Cとの連係

引続きアセンブリ言語を記述するやり方を書きます.


実は関数の中でいきなり全部のレジスタを自由に使える訳ではありません. 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のマクロを駆使すれば自動化もできなくはないです.


御意見は光成滋生<herumi@nifty.com>までお願いします