Cの呼び出し規則
11958 ワード
C言語では、このような関数があると仮定します.
この関数は、呼び出し時にresult=function(1,2)という方法で使用できます.しかし、高度な言語がコンピュータで認識できるマシンコードにコンパイルされると、CPUでは、コンピュータが1つの関数呼び出しにどれだけのパラメータが必要か、どのようなパラメータが必要かを知ることができず、ハードウェアがこれらのパラメータを保存することができないという問題が明らかになった.すなわち,計算機はこの関数にパラメータをどのように伝達するか分からず,パラメータを伝達する作業は関数呼び出し者と関数自体で協調しなければならない.このため、コンピュータは、パラメータ伝達をサポートするスタックと呼ばれるデータ構造を提供する.
スタックは先進的なデータ構造であり、スタックにはストレージ領域とスタックトップポインタがある.スタックトップポインタは、スタック内で最初に使用可能なデータ項目(スタックトップと呼ばれる)を指します.ユーザーは、スタックトップアップ方向スタックにデータを追加することができます.この操作は、スタック(Push)と呼ばれ、スタックを押すと、スタックトップは自動的に新しいデータ項目を追加する位置になり、スタックトップポインタも変更されます.ユーザはスタックからスタックトップを取り出すこともでき、ポップアップスタック(pop)と呼ばれ、ポップアップスタックの後、スタックトップの下の要素がスタックトップになり、スタックトップポインタが変更されます.
関数呼び出しの場合,呼び出し者はパラメータを順次スタックに圧し,関数を呼び出し,関数が呼び出された後,スタックでデータを取得し,計算を行う.関数計算が終了した後、呼び出し者、または関数自体がスタックを修正し、スタックを元の状態に戻します.パラメータ伝達では、2つの重要な問題が明確に説明されなければなりません.
高度な言語では、関数呼び出し規則によってこの2つの問題を説明します.一般的な呼び出し規則は次のとおりです.
stdcall呼び出し規則
stdcallはpascal呼び出し規則と呼ばれることが多い.pascalは初期によく見られた教育用コンピュータプログラム設計言語であり、文法が厳格で、使用される関数呼び出し規則がstdcallであるからだ.Microsoft C++シリーズのC/C++コンパイラでは、この呼び出し規則をPASCALマクロで宣言することがよくあります.同様のマクロにはWINAPIとCALLBACKがあります.
stdcall呼び出し規則宣言の構文は(前述の関数を例に):
stdcallの呼び出し規則は、次のことを意味します.
上記の関数を例にとると、パラメータbはまずスタックされ、次にパラメータaであり、関数呼び出しfunction(1,2)呼び出しでアセンブリ言語に翻訳されると、
関数自体については、次のように翻訳できます.
コンパイル時、この関数の名前は_function@8
注意異なるコンパイラは、コンパイルの汎用性を提供するために独自のアセンブリコードを挿入しますが、一般的にはコードはそうです.ここで、関数の開始にespをebpに保持し、関数の終了リカバリはコンパイラでよく使用される方法です.
関数呼び出しから見ると、2と1は順次pushによってスタックされ、関数ではebp(すなわち、関数が入ったばかりのときのスタックポインタ)に対するオフセット量でパラメータにアクセスする.関数終了後、ret 8は8バイトのスタックをクリーンアップし、関数自身がスタックを復元したことを示す.
cdecl呼び出し規則
cdecl呼び出し規則はC呼び出し規則とも呼ばれ、C言語のデフォルトの呼び出し規則であり、その定義文法は:
本論文を書くとき,予想外にcdecl呼び出しで約束されたパラメータスタックの順序はstdcallと同じであり,パラメータはまず左からスタックに押し込まれることが分かった.異なるのは、関数自体がスタックをクリーンアップせず、呼び出し者がスタックをクリーンアップすることです.このような変化により,C呼び出し約定許容関数のパラメータの個数は固定されず,これもC言語の大きな特色である.前のfunction関数では、cdeclを使用した後の符号化は、次のようになります.
MSDNによると、この修飾は自動的に関数名に先頭の下線を付けるので、関数名は記号表に_として記録される.functionですが、コンパイル中にこのような変化は見られなかったようです.
パラメータは右から左に順番にスタックを押すので、最初のパラメータはスタックの一番上に近い位置にあるので、不定個数パラメータを採用する場合、最初のパラメータのスタック内の位置は、不定のパラメータ個数が最初の後者の後続の明確なパラメータに基づいて決定できる限り、CRTのsprintf関数のような不定パラメータを使用することができることを知っているに違いない.次のように定義します.
すべての不定パラメータはformatで決定できるので,不定個数のパラメータを用いることは問題ない.
fastcall
fastcall呼び出し規則はstdcallと似ています.これは、次のことを意味します.
その宣言構文は、int fastcall function(int a,int b)
thiscall
thiscallはキーワードではないため、明確に指定できない唯一の関数修飾です.これはC++クラスメンバー関数のデフォルトの呼び出し規則です.メンバー関数呼び出しにはthisポインタがあるため、特別な処理が必要です.thiscallは次のことを意味します.
この呼び出し規則を説明するには、次のクラスと使用コードを定義します.
callee関数はアセンブリに翻訳されると次のようになります.
パラメータ個数が固定されている場合、stdcallに類似し、不定時にcdeclに類似していることがわかります.
naked call
これは珍しい呼び出し規則で、一般的なプログラム設計者は使用しないことを提案しています.コンパイラはこの関数に初期化とクリーンアップコードを追加しません.さらに特殊なのは、returnで戻り値を返すことはできません.挿入アセンブリで結果を返すしかありません.これは一般的に実モードドライバの設計に用いられ、合計を求める加算プログラムを定義すると仮定し、以下のように定義することができる.
なお、この関数には明示的なreturn戻り値はなく、eaxレジスタを修正することによって実現され、関数を終了するret命令さえ明示的に挿入しなければならない.上のコードはアセンブリに翻訳されてから次のようになります.
注意この修飾は和_stdcallとcdeclを組み合わせて使用されます.前にcdeclと組み合わせて使用されたコードがあります.stdcallと結合したコードについては、次のようになります.
この関数が呼び出されると、通常のcdeclおよびstdcall呼び出し関数と一致します.
関数呼び出し規則による一般的な問題
定義された規則と使用された規則が一致しない場合、スタックが破壊され、深刻な問題が発生します.次の2つの一般的な問題があります.
後者を例にとると、dllで次の関数を宣言したとします.
使用時のコードは次のとおりです.
呼び出し者が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つを少なくとも理解しました.
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つを少なくとも理解しました.