Visual Studioを連れて行って約束を呼び出す_cdecl、__stdcallと_fastcall


テキストリンク:http://blog.csdn.net/luoweifu/article/details/52425733
C++の開発経験がある人はきっと正しいです.cdecl、__stdcall、__fastcall」はきっと見慣れないでしょう.しかし、あなたは本当に理解しましたか?はい、私はかつてここで無数の穴を掘って、無数のつまずきを植えて、ついに我慢できなくてそれを総括します(私はすでに大部分のこのような問題を解決する能力がありますが)!
呼び出し規則とは
関数の呼び出し規則は、その名の通り関数呼び出しに対する制約と規定(規範)であり、関数パラメータがスタックをどのように伝達し、誰が消去するかを記述している.これは,(1)関数パラメータのスタック順序,(2)呼び出し者がパラメータをスタックにポップアップするか,(3)関数修飾名を生成する方法を決定する.
関数は、次のような値タイプの関数名(パラメータリスト)を返す部分で構成されています.  【code1】
void function();
int add(int a, int b);

以上はよく知られている構成部分ですが、実は関数の構成にはもう一つの部分があります.それは呼び出し約束です.次のようになります.  【code2】
void __cdecl function();
int __stdcall add(int a, int b);

上の_cdeclと_stdcallは呼び出し規則であり、その中で_cdeclはCとC++のデフォルトの呼び出し規則であるため、通常、私たちのコードは【code 1】のように定義され、コンパイラのデフォルトでは__が使用されます.cdecl呼び出し規則.一般的な呼び出し規則には__があります.cdecl、__stdcall、fastcall、最も広く応用されているのは_cdeclと_stdcall、以下で詳しく説明します.いくつかの珍しいものがあります.pascal、__thiscall、__vectorcall.
宣言と定義の呼び出し規則は同じでなければなりません
VC++では、呼び出し約定は関数タイプの一部であるため、関数の宣言と定義では呼び出し約定が同じであり、宣言でのみ呼び出し約定があるのではなく、定義では宣言と異なるかはできません.次のようになります.  【code 3】誤った使用1:
int __stdcall add(int a, int b);
int add(int a, int b)
{
    return a + b;
}

エラー:
error C2373: ‘add’: redefinition; different type modifiers  error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
【code 4】誤った使用2:
int  add(int a, int b);
int __stdcall add(int a, int b)
{
    return a + b;
}

エラー:
error C2373: ‘add’: redefinition; different type modifiers  error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’
【code 5】誤った使用3:
int __stdcall add(int a, int b);
int __cdecl add(int a, int b)
{
    return a + b;
}

エラー:
error C2373: ‘add’: redefinition; different type modifiers  error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
【code 6】正しい使い方:
int __stdcall add(int a, int b);
int __stdcall add(int a, int b)
{
    return a + b;
}

関数の呼び出しプロセス
関数呼び出し規則を深く理解するには、関数の呼び出しプロセスと呼び出しの詳細を理解する必要があります.  関数Aが関数Bを呼び出すと仮定し、A関数を「呼び出し者」、B関数を「呼び出し者」と呼ぶ.次のコードのように、ShowResultは呼び出し元であり、addは呼び出し元である.
int add(int a, int b)
{
    return a + b;
}

void ShowResult()
{
    std::cout << add(5, 10) << std::endl;
}

関数呼び出しプロセスは、次のように記述できます.  (1)呼び出し元(A)のスタックのベースアドレス(ebp)をスタックに入れて,以前のタスクの情報を保存する.  (2)次いで、ebpに、呼び出し元(A)のスタックトップポインタ(esp)の値を新しいベースアドレス(すなわち、呼び出し元Bのスタックバック)として付与する.  (3)その後、このベースアドレス(被呼び出し者Bのスタックの底)に対応する空間を開き(一般的にsub命令で)、被呼び出し者Bのスタック空間として使用する.  (4)関数Bが戻ると、現在のスタックフレームのebpから呼び出し元Aのスタックトップ(esp)に復元され、スタックトップ復元関数Bが呼び出される前の位置となる.その後、呼び出し元Aは、復元されたスタックの上部から前のebp値をポップアップすることができる(この値は、関数呼び出しの前にスタックに押し込まれるためである).これにより、ebpおよびespは、いずれも関数Bを呼び出す前の位置、すなわちスタック復元関数Bを呼び出す前の状態を復元する.  このプロセスはAT&Tアセンブリで2つの命令で完了します.すなわち、 
   leave
   ret
               :
  mov   %ebp , %esp
  pop    %ebp

このセクションの参照先:http://blog.csdn.net/zsy2020314/article/details/9429707
__cdeclの特徴
__cdeclはC Declarationの略で、CとC++のデフォルトの関数呼び出し規則を表す.C/C++とMFCXのデフォルト呼び出し規則です.
  • は、パラメータを右から左の順にスタックに押し込む.
  • パラメータは、呼び出し元によってスタックからポップアップされる.転送パラメータのメモリスタックは呼び出し元によって維持され、EAXに戻ります.したがってprintfのような可変パラメータの関数にはこのような約束が必要である.
  • コンパイラは、コンパイル時にこの呼び出しルールの関数に修飾名を生成する際に、出力関数名に下線接頭辞を付けて_function.関数int add(int a,int b)のような修飾名は_add.

  • (1).パラメータが右から左の順序でスタックを押すことを検証するために,次のコードを見ることができ,Debugは単一ステップでデバッグを行い,呼び出しスタックが先にGetC()に入り,GetB()に入り,最後にGetA()に入ることを見ることができる. 
    (2).2つ目の「呼び出し者がパラメータをスタックにポップアップする」は,コンパイラの作業であり,しばらく検証できない.この部分を深く理解するには、アセンブリ言語に関する知識を学ぶ必要があります.
    (3).関数の修飾名,これはコンパイルしたdllにVSの"dumpbin/exportsを用いることができる. ProjectName.dllコマンドを表示するか(後述)、addの検索などの.objファイルを直接開いて対応するメソッド名を検索します.
    コードとプログラムのデバッグの面から考えると、パラメータのスタックの順序とスタックのクリーンアップはあまり注意する必要はありません.これはコンパイラの決定なので、私たちは変えられません.しかし、3つ目は、この点を明らかにしないと、dll、lib、exeなどの複数のライブラリ間で相互に呼び出され、依存しているときに奇妙なエラーが発生することが多いため、私たちを悩ませています.これは後章で詳しく紹介します.
    __stdcallの特徴
    __stdcallはStandard Callの略で、C++の標準呼び出し方式です.もちろんこれはマイクロソフトが定義した標準です.stdcallは、通常、Win 32 API(WINAPIの定義を参照可能)で使用される.
  • は、パラメータを右から左に順にスタックに押し込む.
  • パラメータは、呼び出し元によってスタックからポップアップされる.関数は、終了時にスタックを空にし、EAXに戻ります.
  • __stdcall呼び出し規則出力関数の名前に下線接頭辞を付け、後に「@」記号とそのパラメータのバイト数を付けて、フォーマットは_function@number.関数int sub(int a,int b)のような修飾名は_sub@8.

  • __fastcallの特徴
    __fastcall呼び出しの主な特徴は、レジスタを介してパラメータを転送するため、速いことです.
  • 実際には_fastcallはECXとEDXで前の2つのDWORD以下のパラメータを転送し、残りのパラメータは右から左へスタックを押して転送し、呼び出された関数は戻る前に転送パラメータのメモリスタックをクリーンアップする.
  • __fastcall呼び出し規則出力関数名に「@」記号を付け、後に「@」記号とそのパラメータのバイト数を@形式で表すfunction@number,double multi(double a,double b)のような修飾名は@multi@16.
  • __fastcallと_stdcallは似ていますが、唯一の違いは、最初の2つのパラメータがレジスタを介して転送されることです.注意レジスタを介して転送される2つのパラメータは、左から右、すなわち、1番目のパラメータがECX、2番目のパラメータがEDX、その他のパラメータが右から左へのスタックであり、戻ってもEAXを通過する.

  • 以下の参照事項を参照してください.http://www.3scard.com/index.php?m=blog&f=view&id=10
    __thiscall
    __thiscallは、C++クラスメンバー関数のデフォルトの呼び出し規則ですが、宣言形式は表示されません.C++クラスでは、メンバー関数呼び出しにもう1つのthisポインタパラメータがあるため、thiscall呼び出し規則の特徴を特別に処理する必要があります.
  • パラメータインスタック:パラメータ右から左へインスタック
  • thisポインタスタック:パラメータの個数が決定された場合、thisポインタはecxを介して呼び出し元に渡されます.パラメータの個数が不確定な場合、thisポインタはすべてのパラメータスタックの後にスタックに押し込まれます.
  • スタックリカバリ:パラメータの個数が不定で、呼び出し者はスタックをクリーンアップし、そうでなければ関数は自分でスタックをクリーンアップします.

  • まとめ
    ここでは主にcdecl、_stdcall、__fastcallの3つの違い:
    要点
    __cdecl
    __stdcall
    __fastcall
    パラメータ伝達方式
    右->左
    右->左
    左から4バイト以下の2つのパラメータはそれぞれECXとEDXレジスタに配置され、残りのパラメータは右から左へスタック転送される.
    スタックのクリーンアップ
    呼び出し元のクリーンアップ
    呼び出された関数のクリーンアップ
    呼び出された関数のクリーンアップ
    適用する場合
    C/C+、MFCのデフォルト方式;可変パラメータの場合に使用します.
    Win API
    要求速度が速い
    Cコンパイル修飾規則
    _functionname
    _functionname@number
    @functionname@number