呼出規約

出典: フリー百科事典『ウィキペディア(Wikipedia)』

プログラミングにおける呼出規約 (よびだしきやく) ないし呼出慣例 (よびだしかんれい) はサブルーチンを呼び出す際の標準的な手法を指す。サブルーチンへデータを渡し、戻るべきアドレス(リターンアドレス)を記録し、サブルーチンからデータを受け取るための規則である。一つのプログラムでは、(複数の言語処理系を用いて記述する場合も)同一の呼出規約を守る必要がある。

さまざまな呼出規約があり、引数のコールスタック(以下単にスタックと呼ぶ)への格納法、サブルーチンにデータを渡す方法、サブルーチンからの復帰法、名前修飾が異なる。

目次

[編集] cdecl

インテル80x86ベースのシステム上のCC++では cdecl 呼出規約が使われることが多い。cdeclでは関数への引数は右から左の順でスタックに積まれる。関数の戻り値は EAX(80x86のレジスタの一つ)に格納される。関数内でEAX, ECX, EDXのレジスタを利用することができる。スタックポインタの処理は呼び出し側で行う。

例えば、以下のCプログラムの関数呼び出しは、

 int function(int, int, int);
 int a, b, c, x;
 ...
 x = function(a, b, c);

以下のような機械語を生成する(MASMにおける80x86アセンブリ言語で記述する)

 push c
 push b
 push a
 call function
 add esp, 12 ;スタック上の引数を除去
 mov x, eax

a, b, cの順でCソースコードに記述された引数は、逆の順序c, b, aでスタックに積まれ、call命令でリターンアドレスをスタックに積んだ上でサブルーチンにジャンプする。戻った後に呼び出し側がスタック上の引数データを除去する。

cdecl は通常 80x86 Cコンパイラのデフォルトであるが、他のコンパイラも(Delphiを含むPascalコンパイラ等)、cdecl に呼出規約を変更するオプションを持っている。手動で操作するには、例えば、

 void _cdecl function(params);

とする。_cdeclはプロトタイプ宣言部ないし関数宣言部に書く必要がある。

OS/2上の Virtual Pascal での例を挙げると、

 function MyWndProc(h : HWND; m : ULONG;
                        mp1 : MPARAM; mp2 : MPARAM) : MRESULT; CDECL;

のようにCDECL指令を付ける。このコンパイラは通常下記の Pascal 呼出規約を用いるが、この関数はOS/2のウィンドウシステム (Presentation Manager) から呼ばれるため、システム側に合わせてcdecl呼出規約に変更する必要がある。

[編集] Pascal

Pascal 呼出規約はcdeclの逆である。引数は左から右への順序でスタックに積まれ、スタック上の引数データを除去するのは呼ばれた側(サブルーチン側)である。Cと異なり、引数の個数と型がサブルーチン宣言時点で完全に固定であるため、スタック上の引数データのバイト数はサブルーチン内部で判明している。引数データの除去は実際にはサブルーチンからのリターン時のスタックポインタの調整で行われ、80x86では"RET n"の一命令で実行可能である。cdeclよりわずかながらコードのサイズが小さくなる。本来呼出規約はプログラミング言語とは独立した概念だが、プログラミング言語の影響を受ける例である。

Lisa及び初期のMacintoshのシステム、アプリケーションはPascalで記述されたため、以前のMacintosh Toolboxを利用するには Pascal呼出規約を用いる必要があった。

[編集] 呼出規約と言語仕様

言語仕様と呼出規約は独立した概念だが、上記の例とは逆に、呼出規約が実装レベルで言語仕様に影響を与える場合がある。fooとbarがどちらも関数であるとし、foobar(foo, bar)のように複数の引数をとる関数呼び出しがあるとする。Pascal 呼出規約では引数を左から右に格納するので、fooを先に、barを後に評価するのが自然で効率が高い。cdeclでは逆にbarを先にfooを後に評価すると自然である。これらの呼出規約に沿って「自然に」コンパイラを実装すると、言語仕様を実装レベルで決定してしまうことになる。

例えば次のPascalコードを考える。

 var i : integer;
 ...
 function foo : boolean;
 begin
    foo := i > 0;
    i := 1
 end;

 function bar : boolean;
 begin
    bar := i > 0;
    i := -1
 end;

 function foobar(f, b : boolean) : boolean;
 begin
    foobar := f < b
 end;

 begin
   i := 1;
   write(foobar(foo, bar))
 end

関数呼び出し foobar(foo, bar) を行う際引数のfooとbarのどちらを先に評価するかで、結果が変わる。fooを先に評価すると変数 i はfoo呼び出し後にも1であるので、fooは真、barも真、foobar(foo, bar)は偽となる。barを先に評価すると、barは真だが、呼び出し後に i は-1になるため、fooは偽となり、foobar(foo, bar)は真となる。なお、標準Pascalで は引数を評価する順序を規定しておらず、結果が引数の評価順に依存するプログラムは良くない例だとされる。この例は良くない例である。

[編集] Register (fastcall)

レジスタ呼出規約または fastcall、レジスタ渡し が意味するものは、歴史的な事情からコンパイラ依存であるが、総じて次のようなものである。プロセッサのレジスタのビット幅と個数に合わせて、最初のいくつかの引数を(スタックではなく)レジスタに格納し、サブルーチンに渡す。残りの引数は cdecl と同様に右から左の順にスタックに積む。戻り値はAL, AX, EAXレジスタに格納する。関数の引数の個数が可変でない場合は、スタックからの引数データの除去は(Pascal 呼出規約のように)サブルーチン側で行うのが通例である。しかしながら、ほとんどのランタイムライブラリサブルーチンはわずかな個数の引数しか取らないため、スタックにデータを積む必要は(従ってスタックを清掃する必要も)全くない。メモリよりレジスタの方が読み書きが速いため、レジスタ渡しは効率が高い。

レジスタ渡しの例

  • マイクロソフト(以下MS) __fastcall[1] 呼出規約(別名 __msfastcall)では、最初の2つの引数をECXおよびEDXに格納する。
  • ボーランド __fastcall 呼出規約では、最初の3つの引数をEAX, EDX, ECXに格納する。
  • Watcom __fastcall 呼出規約では、最初の4つの引数をEAX, EDX, EBX, ECXに格納する。

Watcom C/C++ コンパイラでは #pragma aux[2] コンパイラ指令を用いて、プログラマが自前の呼出規約を指定することができる。取扱説明書によると、「"Very few users are likely to need this method, but if it is needed, it can be a lifesaver"(引用部分につき訳さない。ほとんど不要だが、いざという時には必要なものである、程度の意)」だそうである。

[編集] stdcall

MS stdcall [3]Windows APIで利用されているデファクトスタンダードである。引数は右から左に渡される。レジスタEAX, ECX, EDXはサブルーチン内で用いるために保存しておく必要がある。返り値はEAXに格納する。cdeclと異なり、スタックの清掃はサブルーチン側で行う(Pascal 呼出規約と同様)。従って可変長の引数リストはサポートされない。


[編集] safecall

WindowsでのDelphiは、safecallという呼出規約を用いてCOMのエラー処理をカプセル化している。COM/OLE の要求に沿って、例外が呼び出し側に漏れ出すことがなく、戻り値HRESULTに報告される。Delphiのコードからsafecallを行うと、自動的にHRESULTがチェックされ、必要なら例外が発生する。COMインタフェースの言語レベルでのサポートと自動的なIUnknownの扱い(暗黙のAddRef/Release/QueryInterface呼び出し)のおかげで、DelphiでのCOM/OLEプログラミングは容易でエレガントなものになっている。

この節は執筆の途中です この節は執筆中です。加筆、訂正して下さる協力者を求めています

[編集] thiscall

この規約はC++のメンバ関数を呼ぶのに用いられる。二つの主たる版があり、コンパイラ及び、関数が可変長の引数リストを用いるかによっていずれかが用いられる。

GCC (GNU C コンパイラ)では、thiscall はほとんど cdecl と同様である。呼び出し側がスタックを清掃し、引数は右から左の順で渡される。相違点は全ての引数を渡した後、最後に this ポインタがスタックに積まれることである。これは Windows で可変引数を用いるときの thiscall と類似している。

Windowsの場合は、関数が可変引数を取らない場合、引数は右から左に渡され、thisポインタはECXレジスタに格納される。スタックを清掃するのは呼ばれた側である。

thiscallがキーワードではないため、thiscallを明示的に指定することはできないが、IDAのような逆アセンブラではそれを指定する必要がある。そのため、__thiscall__というキーワードが用意されている。

[編集] clrcall

未稿

この節は執筆の途中です この節は執筆中です。加筆、訂正して下さる協力者を求めています

[編集] Intel ABI

インテルApplication Binary Interface (ABI) はほとんどのコンパイラが採用している呼出規約である。EAX, EDX, ECXはサブルーチン内で自由に用いることができなければならず、保護されてはいけない。

[編集] マイクロソフト x64呼出規約

x64呼出規約はx86-64 / EM64Tで追加されたレジスタ空間を有益に利用する。RCX, RDX, R8, R9レジスタは整数型とポインタ型の引数に、XMM0, XMM1, XMM2, XMM3は浮動小数点型引数に用いられる。レジスタが足りなくなれば、スタックが用いられる。返り値はRAXに格納される。

[編集] 標準サブルーチンプロローグ/エピローグ

サブルーチンに制御が渡った点で、標準的には次のような処理を行う:

 _function:
     push ebp       ;ベースポインタを保存
     mov ebp, esp   ;現在のスタックフレームを指すようベースポインタを変更
     sub esp, x     ;局所変数(Cでいう自動変数)の大きさの分スタックポインタを減らす

この結果、EBPはスタック上の引数の頭を指し、局所変数を格納する領域をスタック上に確保することができる。元のEBPの値はスタックに保存されている。このように局所変数は引数と同様にサブルーチン呼び出し毎にスタック上に確保されるので、再帰呼び出しが可能になる。

このサブルーチンから抜け出す際は、次のシーケンスを実行する:

    mov esp, ebp   ;局所変数を除去
    pop ebp        ;ベースポインタを復帰
    ret            ;サブルーチンから戻る

これはcdeclの例であって、Pascal 呼出規約では次のように引数データをサブルーチン側が掃除する。

    ret n          ;nは引数データのバイト数

次のC関数は

 int _cdecl MyFunction(int i){ 
     int k;
     return i + k;
 }

次のアセンブラコードと同等である。

    ;エントリシーケンス
    push ebp
    mov ebp, esp
    sub esp, 4     ;整数型変数kの領域に相当
 
    ;関数のコード
    mov eax, [ebp + 8] 
                   ;引数iを加算器(アキュムレータ)に移動
    add eax, [ebp - 4]
                   ;kをiに加える
                   ;結果はEAX(加算器)に残る
 
    ;終了シーケンス
    mov esp, ebp
    pop ebp
    ret

[編集] 関連項目

[編集] 外部リンク