99

stdcallcdeclという 2 種類の呼び出し規則があります。それらについていくつか質問があります。

  1. cdecl 関数が呼び出されたとき、呼び出し元はスタックを解放する必要があるかどうかをどのように判断しますか? 呼び出しサイトで、呼び出し元は、呼び出されている関数が cdecl 関数か stdcall 関数かを認識していますか? それはどのように機能しますか?呼び出し元は、スタックを解放する必要があるかどうかをどのように判断しますか? それともリンカーの責任ですか?
  2. stdcall として宣言されている関数が関数 (cdecl として呼び出し規則を持つ) を呼び出す場合、またはその逆の場合、これは不適切でしょうか?
  3. 一般的に、 cdecl と stdcall のどちらの呼び出しの方が速いと言えますか?
4

9 に答える 9

85

Raymond Chen が、何と何をするかについて素晴らしい概要を説明し__stdcall__cdeclます。

(1) コンパイラはその関数の呼び出し規則を認識して必要なコードを生成するため、呼び出し元は、関数を呼び出した後にスタックをクリーンアップすることを「知っています」。

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

次のように、呼び出し規約が一致しない可能性があります。

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

非常に多くのコード サンプルがこれを間違っており、面白くありません。次のようになります。

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

ただし、プログラマがコンパイラ エラーを無視しないと仮定すると、コンパイラは関連する関数の呼び出し規則を認識しているため、スタックを適切にクリーンアップするために必要なコードを生成します。

(2) どちらの方法でも機能するはずです。実際、これは、少なくとも Windows API と対話するコードで非常に頻繁に発生します。これ__cdeclは、Visual C++ コンパイラによる C および C++ プログラムの既定値であり、WinAPI 関数が__stdcall規則を使用するためです。

(3) 両者の間に実際のパフォーマンスの違いはないはずです。

于 2010-08-04T09:58:15.737 に答える
46

CDECL引数が逆の順序でスタックにプッシュされると、呼び出し元はスタックをクリアし、結果はプロセッサレジストリを介して返されます(後で「レジスタA」と呼びます)。STDCALLには、1つの違いがあります。呼び出し元はスタックをクリアしませんが、呼び出し元はクリアします。

あなたはどちらが速いかを尋ねています。誰も。可能な限り、ネイティブの呼び出し規約を使用する必要があります。特定の規則を使用する必要がある外部ライブラリを使用するときに、抜け道がない場合にのみ規則を変更します。

さらに、コンパイラがデフォルトとして選択できる他の規則があります。つまり、Visual C ++コンパイラはFASTCALLを使用します。これは、プロセッサレジスタがより広範囲に使用されるため、理論的には高速です。

通常、外部ライブラリに渡されるコールバック関数に適切な呼び出し規約の署名を与える必要があります。つまりqsort、CライブラリからのコールバックはCDECLである必要があります(コンパイラがデフォルトで他の規約を使用する場合は、コールバックをCDECLとしてマークする必要があります)。 STDCALL(WinAPI全体がSTDCALLです)。

他の通常のケースは、いくつかの外部関数へのポインタを格納している場合です。つまり、WinAPI関数へのポインタを作成するには、その型定義をSTDCALLでマークする必要があります。

以下は、コンパイラがどのようにそれを行うかを示す例です。

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL:

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)
于 2010-08-04T10:28:34.657 に答える
16

__stdcallaから aを呼び出す__cdeclか、その逆を呼び出すかは問題ではないという投稿に気付きました。します。

理由: __cdecl呼び出された関数に渡された引数は、呼び出し元の関数によって__stdcallスタックから削除されます。 では、引数は呼び出された関数によってスタックから削除されます。__cdeclを使用して関数を呼び出した場合__stdcall、スタックはまったくクリーンアップされないため、最終的に が__cdecl引数またはリターン アドレスにスタック ベースの参照を使用すると、現在のスタック ポインターの古いデータが使用されます。__stdcallから関数を呼び出すと、__cdecl関数__stdcallはスタック上の引数をクリーンアップし、次に__cdecl関数が再度実行して、呼び出し元の関数の戻り情報を削除する可能性があります。

Microsoft の C の規則では、名前を変更することでこれを回避しようとしています。__cdecl関数の先頭にはアンダースコアが付きます。関数の__stdcall前にアンダースコアを付け、末尾にアットマーク「@」と削除するバイト数を付けます。たとえば、 __cdeclf(x) は としてリンクされ、_fは4 バイトとしてリンクされます)__stdcall f(int x)_f@4sizeof(int)

リンカを通過できた場合は、デバッグの混乱を楽しんでください。

于 2012-09-17T18:29:39.623 に答える
2

関数型で指定します。関数ポインタがある場合、明示的に stdcall でない場合は cdecl と見なされます。これは、stdcall ポインターと cdecl ポインターを取得した場合、それらを交換できないことを意味します。2 つの関数型は、問題なく相互に呼び出すことができます。一方の型を取得すると、もう一方の型が取得されるだけです。速度に関しては、どちらも同じ役割を果たしますが、場所がわずかに異なるだけで、まったく関係ありません。

于 2010-08-04T09:59:43.627 に答える
1

呼び出し元と呼び出し先は、呼び出しの時点で同じ規則を使用する必要があります。これが、確実に機能する唯一の方法です。呼び出し元と呼び出し先の両方が、定義済みのプロトコルに従います。たとえば、誰がスタックをクリーンアップする必要があるかなどです。慣例が一致しない場合、プログラムは未定義の動作に陥ります。おそらく、見事にクラッシュするだけです。

これは、呼び出しサイトごとにのみ必要です。呼び出しコード自体は、任意の呼び出し規則を持つ関数にすることができます。

これらの規則間でのパフォーマンスの実際の違いに気付くべきではありません。それが問題になる場合は、通常、呼び出しを減らす必要があります。たとえば、アルゴリズムを変更します。

于 2010-08-04T09:59:06.353 に答える
1

これらは、コンパイラ固有およびプラットフォーム固有です。C も C++ 標準も、C++ を除いて呼び出し規約について何も述べていませんextern "C"

呼び出し元は、スタックを解放する必要があるかどうかをどのように知ることができますか?

呼び出し元は、関数の呼び出し規則を認識しており、それに応じて呼び出しを処理します。

呼び出しサイトで、呼び出し元は、呼び出されている関数が cdecl 関数か stdcall 関数かを認識していますか?

はい。

それはどのように機能しますか?

関数宣言の一部です。

呼び出し元は、スタックを解放する必要があるかどうかをどのように判断しますか?

発信者は呼び出し規約を知っているので、それに応じて行動できます。

それともリンカーの責任ですか?

いいえ、呼び出し規約は関数の宣言の一部であるため、コンパイラは知る必要があることをすべて知っています。

stdcall として宣言されている関数が関数 (cdecl として呼び出し規則を持つ) を呼び出す場合、またはその逆の場合、これは不適切でしょうか?

いいえ、なぜそうすべきなのですか?

一般的に、 cdecl と stdcall のどちらの呼び出しの方が速いと言えますか?

知らない。試して。

于 2010-08-04T10:01:55.693 に答える
0

呼び出し規約は、C/C++ プログラミング言語とは関係なく、コンパイラが特定の言語を実装する方法に固有のものです。一貫して同じコンパイラを使用する場合、呼び出し規約について心配する必要はありません。

ただし、異なるコンパイラでコンパイルされたバイナリ コードを相互に正しく動作させたい場合があります。その際、Application Binary Interface (ABI) と呼ばれるものを定義する必要があります。ABI は、コンパイラが C/C++ ソースをマシンコードに変換する方法を定義します。これには、呼び出し規則、名前マングリング、および v-table レイアウトが含まれます。cdelc と stdcall は、x86 プラットフォームで一般的に使用される 2 つの異なる呼び出し規則です。

呼び出し規則に関する情報をソース ヘッダーに配置することにより、コンパイラは、指定された実行可能ファイルと正しく相互運用するために生成する必要があるコードを認識します。

于 2010-08-04T10:16:31.027 に答える
0

a) cdecl 関数が呼び出し元によって呼び出された場合、呼び出し元はスタックを解放する必要があるかどうかをどのように知ることができますか?

修飾子は関数プロトタイプ (または関数ポインター型など)のcdecl一部であるため、呼び出し元はそこから情報を取得し、それに応じて動作します。

b) stdcall として宣言された関数が関数 (cdecl として呼び出し規則を持つ) を呼び出す場合、またはその逆の場合、これは不適切でしょうか?

大丈夫です。

c) 一般的に、cdecl と stdcall のどちらの呼び出しが速いと言えますか?

一般的に、私はそのような発言を控えます。区別は重要です。va_arg 関数を使用する場合。理論的にはstdcall、引数のポップとローカルのポップを組み合わせることができるため、より高速で小さなコードを生成する可能性がありますが、OTOH を使用するとcdecl、賢い場合は同じこともできます。

高速化を目的とした呼び出し規約では、通常、何らかのレジスター渡しが行われます。

于 2010-08-04T10:05:46.950 に答える