Cの呼び出し規則

11958 ワード

C言語では、このような関数があると仮定します.
int function(int a,int b)

この関数は、呼び出し時にresult=function(1,2)という方法で使用できます.しかし、高度な言語がコンピュータで認識できるマシンコードにコンパイルされると、CPUでは、コンピュータが1つの関数呼び出しにどれだけのパラメータが必要か、どのようなパラメータが必要かを知ることができず、ハードウェアがこれらのパラメータを保存することができないという問題が明らかになった.すなわち,計算機はこの関数にパラメータをどのように伝達するか分からず,パラメータを伝達する作業は関数呼び出し者と関数自体で協調しなければならない.このため、コンピュータは、パラメータ伝達をサポートするスタックと呼ばれるデータ構造を提供する.
スタックは先進的なデータ構造であり、スタックにはストレージ領域とスタックトップポインタがある.スタックトップポインタは、スタック内で最初に使用可能なデータ項目(スタックトップと呼ばれる)を指します.ユーザーは、スタックトップアップ方向スタックにデータを追加することができます.この操作は、スタック(Push)と呼ばれ、スタックを押すと、スタックトップは自動的に新しいデータ項目を追加する位置になり、スタックトップポインタも変更されます.ユーザはスタックからスタックトップを取り出すこともでき、ポップアップスタック(pop)と呼ばれ、ポップアップスタックの後、スタックトップの下の要素がスタックトップになり、スタックトップポインタが変更されます.
関数呼び出しの場合,呼び出し者はパラメータを順次スタックに圧し,関数を呼び出し,関数が呼び出された後,スタックでデータを取得し,計算を行う.関数計算が終了した後、呼び出し者、または関数自体がスタックを修正し、スタックを元の状態に戻します.パラメータ伝達では、2つの重要な問題が明確に説明されなければなりません.
  1)           ,             ;
2)      ,          。

高度な言語では、関数呼び出し規則によってこの2つの問題を説明します.一般的な呼び出し規則は次のとおりです.
stdcall
cdecl
fastcall
thiscall
naked call

stdcall呼び出し規則
stdcallはpascal呼び出し規則と呼ばれることが多い.pascalは初期によく見られた教育用コンピュータプログラム設計言語であり、文法が厳格で、使用される関数呼び出し規則がstdcallであるからだ.Microsoft C++シリーズのC/C++コンパイラでは、この呼び出し規則をPASCALマクロで宣言することがよくあります.同様のマクロにはWINAPIとCALLBACKがあります.
stdcall呼び出し規則宣言の構文は(前述の関数を例に):
int __stdcall function(int a,int b)

stdcallの呼び出し規則は、次のことを意味します.
  1)          ;
2)        ;
3)            ,      @  ,          。

上記の関数を例にとると、パラメータbはまずスタックされ、次にパラメータaであり、関数呼び出しfunction(1,2)呼び出しでアセンブリ言語に翻訳されると、
  push 2                 
  push 1                 
  call function       ,       cs:eip  

関数自体については、次のように翻訳できます.
  push  ebp                 ebp   ,                ,          
  mov   ebp,esp                 
  mov   eax,[ebp + 8H]       ebp           ebp,cs:eip,a,b,ebp +8  a
  add   eax,[ebp + 0CH]      ebp + 12    b
  mov   esp,ebp             esp
  pop   ebp
  ret   8

コンパイル時、この関数の名前は_function@8
注意異なるコンパイラは、コンパイルの汎用性を提供するために独自のアセンブリコードを挿入しますが、一般的にはコードはそうです.ここで、関数の開始にespをebpに保持し、関数の終了リカバリはコンパイラでよく使用される方法です.
関数呼び出しから見ると、2と1は順次pushによってスタックされ、関数ではebp(すなわち、関数が入ったばかりのときのスタックポインタ)に対するオフセット量でパラメータにアクセスする.関数終了後、ret 8は8バイトのスタックをクリーンアップし、関数自身がスタックを復元したことを示す.
cdecl呼び出し規則
cdecl呼び出し規則はC呼び出し規則とも呼ばれ、C言語のデフォルトの呼び出し規則であり、その定義文法は:
  int function (int a ,int b)           //       C    
  int __cdecl function(int a,int b)     //     C    

本論文を書くとき,予想外にcdecl呼び出しで約束されたパラメータスタックの順序はstdcallと同じであり,パラメータはまず左からスタックに押し込まれることが分かった.異なるのは、関数自体がスタックをクリーンアップせず、呼び出し者がスタックをクリーンアップすることです.このような変化により,C呼び出し約定許容関数のパラメータの個数は固定されず,これもC言語の大きな特色である.前のfunction関数では、cdeclを使用した後の符号化は、次のようになります.
     
  push   1
  push   2
  call   function
  add    esp,8                :          
       _function 
  push   ebp                  ebp   ,                ,          
  mov    ebp,esp                  
  mov    eax,[ebp + 8H]        ebp           ebp,cs:eip,a,b,ebp +8  a
  add    eax,[ebp + 0CH]       ebp + 12    b
  mov    esp,ebp              esp
  pop    ebp
  ret                         ,        

MSDNによると、この修飾は自動的に関数名に先頭の下線を付けるので、関数名は記号表に_として記録される.functionですが、コンパイル中にこのような変化は見られなかったようです.
パラメータは右から左に順番にスタックを押すので、最初のパラメータはスタックの一番上に近い位置にあるので、不定個数パラメータを採用する場合、最初のパラメータのスタック内の位置は、不定のパラメータ個数が最初の後者の後続の明確なパラメータに基づいて決定できる限り、CRTのsprintf関数のような不定パラメータを使用することができることを知っているに違いない.次のように定義します.
int sprintf(char* buffer,const char* format,...)

すべての不定パラメータはformatで決定できるので,不定個数のパラメータを用いることは問題ない.
fastcall
fastcall呼び出し規則はstdcallと似ています.これは、次のことを意味します.
  1)           DWORD  (       )  ecx edx  ,               ;
2)          ;
3)         stdcall。

その宣言構文は、int fastcall function(int a,int b)
thiscall
thiscallはキーワードではないため、明確に指定できない唯一の関数修飾です.これはC++クラスメンバー関数のデフォルトの呼び出し規則です.メンバー関数呼び出しにはthisポインタがあるため、特別な処理が必要です.thiscallは次のことを意味します.
  1)         ;
2)         ,this    ecx       ;         ,this               ;
3)         ,       ,          。

この呼び出し規則を説明するには、次のクラスと使用コードを定義します.
class A
{
public:
int function1(int a,int b);
int function2(int a,...);
};
int A::function1 (int a,int b)
{
return a+b;
}
int A::function2(int a,...)
{
va_list ap;
va_start(ap,a);
int i;
int result = 0;
for(i = 0 ; i < a ; i ++)
{
result += va_arg(ap,int);
}
return result;
}
void callee()
{
A a;
a.function1(1,2);
a.function2(3,1,2,3);
}

callee関数はアセンブリに翻訳されると次のようになります.
  //   function1  
  0401C1D    push        2
  00401C1F   push        1
  00401C21   lea         ecx,[ebp-8]
  00401C24   call   function1               ,  this     
  //   function2  
  00401C29   push        3
  00401C2B   push        2
  00401C2D   push        1
  00401C2F   push        3
  00401C31   lea         eax,[ebp-8]          this  
  00401C34   push        eax
  00401C35   call   function2
  00401C3A   add         esp,14h

パラメータ個数が固定されている場合、stdcallに類似し、不定時にcdeclに類似していることがわかります.
naked call
これは珍しい呼び出し規則で、一般的なプログラム設計者は使用しないことを提案しています.コンパイラはこの関数に初期化とクリーンアップコードを追加しません.さらに特殊なのは、returnで戻り値を返すことはできません.挿入アセンブリで結果を返すしかありません.これは一般的に実モードドライバの設計に用いられ、合計を求める加算プログラムを定義すると仮定し、以下のように定義することができる.
   __declspec(naked) int  add(int a,int b)
   {
       __asm mov eax,a
       __asm add eax,b
       __asm ret
   }

なお、この関数には明示的なreturn戻り値はなく、eaxレジスタを修正することによって実現され、関数を終了するret命令さえ明示的に挿入しなければならない.上のコードはアセンブリに翻訳されてから次のようになります.
   mov    eax,[ebp+8]
   add    eax,[ebp+12]
   ret    8

注意この修飾は和_stdcallとcdeclを組み合わせて使用されます.前にcdeclと組み合わせて使用されたコードがあります.stdcallと結合したコードについては、次のようになります.
   __declspec(naked) int __stdcall function(int a,int b)
   {
       __asm mov eax,a
       __asm add eax,b
       __asm ret 8        //     8
   }

この関数が呼び出されると、通常のcdeclおよびstdcall呼び出し関数と一致します.
関数呼び出し規則による一般的な問題
定義された規則と使用された規則が一致しない場合、スタックが破壊され、深刻な問題が発生します.次の2つの一般的な問題があります.
1)                
2) DLL               

後者を例にとると、dllで次の関数を宣言したとします.
   __declspec(dllexport) int func(int a,int b);    //  ,    stdcall,    cdecl

使用時のコードは次のとおりです.
   ...
   typedef int (*WINAPI DLLFUNC)func(int a,int b);
   hLib = LoadLibrary(...);
   DLLFUNC func = (DLLFUNC)GetProcAddress(...);   //         
   result = func(1,2);  //    
   ...

呼び出し者がWINAPIの意味を理解していないため、誤ってこの修飾が追加され、上記のコードは必然的にスタックが破壊され、MFCがコンパイル時に挿入したcheckesp関数はスタックが破壊されたことを教えてくれます.WINAPIとCALLBACKの2つのマクロについて:
WINAPI  ·Use in place of FAR PASCAL in API declarations. If you are writing a DLL with exported API entry points, you can use this for your own APIs. 
CALLBACK ·Use in place of FAR PASCAL in application callback routines such as window procedures and dialog procedures. 
この2つのマクロの内容を見てみましょう
VC:WINDEF.h #define CALLBACK    PASCAL //=_pascal,VCは既に直接使用をサポートしていません_pascal了#define WINAPI CDECL/=cdecl
BCB:windef.h #define CALLBACK    __stdcall #define WINAPI      __stdcall
cdecl stdcallなど珍しいキーワードを引き出しました
ではcdecl、pascal、stdcall、fastcallなどの修飾記号はいったいどういう意味なのでしょうか.非常に簡単で、スタックについてのいくつかの説明で、まず関数パラメータのスタック順序で、次にスタックに圧入された内容は誰がクリアしますか?呼び出し者ですか?それとも関数自身ですか?これらのスイッチは、コンパイラがどのようなアセンブリコードを生成するかを示すために使用されます.
以下に区別のリストを示します.
Directive Parameter order  Clean-up Passes parameters in registers?  register  Left-to-right   Routine   Yes  pascal   Left-to-right   Routine   No  cdecl   Right-to-left   Caller    No  stdcall   Right-to-left   Routine   No  safecall  Right-to-left   Routine   No
簡単な説明:
__cdeclはC/C++とMFCプログラムのデフォルトで使用される呼び出し規則であり、関数宣言時に__を付けることもできる.cdeclキーワードは手動で指定します.採用__cdeclが約束すると、関数パラメータは右から左の順にスタックに入り、呼び出し関数者がパラメータをスタックにポップアップしてスタックをクリーンアップします.したがって、可変パラメータを実装する関数は、この呼び出し規則のみを使用できます.それぞれ使用するためcdeclで約束された関数には、スタックをクリーンアップするコードが含まれているため、実行可能なファイルのサイズが大きくなります.cdeclは_と書くことができますcdecl.         __stdcall呼び出し規則は、Win 32 API関数を呼び出すために使用される.採用__stdcal約定の場合,関数パラメータは右から左の順にスタックに入り,呼び出された関数は戻る前に転送パラメータのスタックをクリーンアップし,関数パラメータの個数は固定する.関数体自体が伝達されたパラメータの個数を知っているため、呼び出された関数は、戻る前にret n命令で伝達パラメータのスタックを直接クリーンアップすることができる.stdcallは_と書くことができますstdcall.         __fastcallでは、性能に対する要求が非常に高い場合に使用されます.fastcallは、関数の左から4バイト以下の2つのパラメータをそれぞれECXとEDXレジスタに配置することを約束し、残りのパラメータは右から左へスタックを押して転送され、呼び出された関数は戻る前に転送パラメータのスタックをクリーンアップする.__fastcallは_fastcallと書くことができる.
・特に説明1.デフォルトでは、__を使用します.cdecl方式であるため省略することができる.2.WINAPIは、一般的に動的リンクライブラリのエクスポート関数を修飾するために使用される.CALLBACKはコールバック関数4を修飾するためにのみ使用する.VCの下とBCBの下でWINAPIの定義が異なることに気づいたかもしれませんが、BCBの下からVCのdllを直接呼び出すことができない理由の1つを少なくとも理解しました.