この記事は検証可能な参考文献や出典が全く示されていないか、不十分です。出典を追加して記事の信頼性向上にご協力ください。(このテンプレートの使い方)
出典検索?: "呼出規約"
呼出規約(よびだしきやく)ないし呼出慣例(よびだしかんれい)(英: calling convention)は、コンピュータの命令セットアーキテクチャごとに取り決められるABIの一部で、サブルーチンが呼出される際に従わねばならない制限などの標準である。名前修飾について、データを渡す「実引数」、戻るべきアドレスである「リターンアドレス」、データを戻す「返戻値」などを、スタックなどに対してどのように格納するのか、また各レジスタを、呼び出し側とサブルーチンのどちらの側が保存するか、等といった取決めの集まりである。言語が同じでも、分割コンパイルされリンカでリンクされる相互のプロシージャ間では、呼出し呼出されるならば同一の呼出規約に従っていなければならない。一方で、違う言語の間でも、同一の呼出規約を経由して相互にプロシージャを呼出すこともできる。 インテルx86ベースのシステム上のC/C++では cdecl 呼出規約が使われることが多い。cdeclでは関数への引数は右から左の順でスタックに積まれる。関数の戻り値は EAX(x86のレジスタの一つ)に格納される。呼び出された側の関数ではEAX, ECX, EDXのレジスタの元の値を保存することなく使用してよい。呼び出し側の関数では必要ならば呼び出す前にそれらのレジスタをスタック上などに保存する。スタックポインタの処理は呼び出し側で行う。 例えば、以下のCプログラムの関数呼び出しは、int function(int, int, int);int a, b, c, x;...x = function(a, b, c); 以下のような機械語を生成する(MASMにおけるx86アセンブリ言語で記述する)。push cpush bpush acall functionadd esp, 12 ;スタック上の引数を除去mov x, eax Cソースコードにてa, b, cの順で記述された引数は、逆の順序c, b, aでスタックに積まれ、call命令でリターンアドレスをスタックに積んだ上でサブルーチンにジャンプする。戻った後に呼び出し側がスタック上の引数データを除去する。 cdecl は通常 x86 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 呼出規約はcdeclの逆である。引数は左から右への順序でスタックに積まれ、スタック上の引数データを除去するのは呼ばれた側(サブルーチン側)である。Cと異なり、引数の個数と型がサブルーチン宣言時点で完全に固定であるため、スタック上の引数データのバイト数はサブルーチン内部で判明している。引数データの除去は実際にはサブルーチンからのリターン時のスタックポインタの調整で行われ、x86では"RET n"の一命令で実行可能である。サブルーチンのコードサイズはわずかながら増えることが多いが、呼び出し側でスタックを処理するコードが不要になるため全体としてはわずかながらコードのサイズが小さくなることが多い。本来呼出規約はプログラミング言語とは独立した概念だが、プログラミング言語の影響を受ける例である。 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 := 1end;function bar : Boolean;begin bar := i > 0; i := -1end;function foobar(f, b : Boolean) : Boolean;begin foobar := f < bend;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では引数を評価する順序を規定しておらず、結果が引数の評価順に依存するプログラムは良くない例だとされる。この例は良くない例である。 レジスタ呼出規約または fastcall、レジスタ渡し が意味するものは、歴史的な事情からコンパイラ依存であるが、総じて次のようなものである。プロセッサのレジスタのビット幅と個数に合わせて、最初のいくつかの引数を(スタックではなく)レジスタに格納し、サブルーチンに渡す。残りの引数は cdecl と同様に右から左の順にスタックに積まれる[1]。戻り値はAL, AX, EAXレジスタに格納する。関数の引数の個数が可変でない場合は、スタックからの引数データの除去は(Pascal 呼出規約のように)サブルーチン側で行うのが通例である。しかしながら、ほとんどのランタイムライブラリサブルーチンはわずかな個数の引数しか取らないため、スタックにデータを積む必要は(従ってスタックを清掃する必要も)全くない。メモリよりレジスタの方が読み書きが速いため、レジスタ渡しは効率が高い。 レジスタ渡しの例
cdecl
Pascal
呼出規約と言語仕様
Register (fastcall)
マイクロソフト(以下MS) __fastcall ⇒[1] ⇒[2] 呼出規約(別名 __msfastcall)では、最初の2つの引数をECXおよびEDXに格納する。
Size:19 KB
出典: フリー百科事典『ウィキペディア(Wikipedia)』
担当:undef