讲解一:转自 http://blog.csdn.net/olenet/article/details/12782331
(1) _stdcall调用
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
#define WINAPI _stdcall
按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如。
(2) _cdecl调用
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。关于C/C++中变长参数(…)的问题,笔者将另文详述。
由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
(3) _fastcall调用
_fastcall调用较快,它通过CPU内部寄存器传递参数。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。
几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,为什么??
首先,我们谈一下两者之间的区别:
WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数
调用完成后,栈需要清除,这里就是问题的关键,如何清除??
如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲
就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同
,那么调用者能否正常的完成清除工作呢?答案是不能。
如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨
(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。
那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变
的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行
,因此,这种情况我们只能使用_cdecl。
到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcal
讲解二:转自http://www.jb51.net/article/34554.htm
1. __cdecl
__cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容,一般来说,这是 C/C++ 的默认调用函数的规则,MS VC 编译器采用的规则则是这种规则2. __stdcall
_stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容,Windows API 所采用的函数调用规则则是这种规则
另外,采用 __cdecl 和 __stdcall 不同规则的函数所生成的修饰名也各为不同,相同点则是生成的函数修饰名前缀都带有下划线,不同的则是后缀部分,当然,这两者最大的不同点就在于恢复栈的方式不同,而且这点亦是最为重要的。
__cdecl 规则要求调用者本身负责栈的恢复工作,在汇编的角度上说,恢复堆栈的位置是在调用函数内,考虑这样一段 C++ 代码(在 VC 下 Debug)
#include <cstdio>
void __cdecl func(int param1, int param2, int param3) {
int var1 = param1;
int var2 = param2;
int var3 = param3;
printf("%ld\n", long(¶m1));
printf("%ld\n", long(¶m2));
printf("%ld\n", long(¶m3));
printf("----------------\n");
printf("%ld\n", long(&var1));
printf("%ld\n", long(&var2));
printf("%ld\n", long(&var3));
return ;
}
int main() {
func(1, 2, 3);
return 0;
}
注意到 func 函数使用了 __cdecl 进行修饰(其实不需要,因为 VC 下默认的是 __cdecl 规则, 这里是为了更为清晰),生成汇编代码如下:
3: void __cdecl func(int param1, int param2, int param3) {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,4Ch
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-4Ch]
0040102C mov ecx,13h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
4: int var1 = param1;
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax ; 注意var1,var2,var3 压入堆栈的顺序!
5: int var2 = param2;
0040103E mov ecx,dword ptr [ebp+0Ch]
00401041 mov dword ptr [ebp-8],ecx
6: int var3 = param3;
00401044 mov edx,dword ptr [ebp+10h]
00401047 mov dword ptr [ebp-0Ch],edx
............................................... ; 省略了printf的代码
15: return ;
16: }
004010BD pop edi
004010BE pop esi
004010BF pop ebx
004010C0 add esp,4Ch
004010C3 cmp ebp,esp
004010C5 call __chkesp (004011d0)
004010CA mov esp,ebp
004010CC pop ebp
004010CD ret ; 这里是 ret,由调用者(main)恢复堆栈,但如果是 __stdcall 的话,
; 恢复堆栈就在这里进行
*******************************************************************************************************************
18: int main() {
............................................... ; 省略了建立堆栈的代码
19: func(1, 2, 3);
00401118 push 3 ; 将 param3 压入栈
0040111A push 2 ; 将 param2 压入栈
0040111C push 1 ; 将 param1 压入栈
0040111E call @ILT+5(func) (0040100a) ; @ILT+5(func) 是函数func的修饰名,而0040100a则是他的地址
00401123 add esp,0Ch ; 恢复堆栈,__cdecl 规则由调用者(这里是main)恢复堆栈
20: return 0;
00401126 xor eax,eax
21: }
00401128 pop edi
00401129 pop esi
0040112A pop ebx
0040112B add esp,40h
0040112E cmp ebp,esp
00401130 call __chkesp (004011d0)
00401135 mov esp,ebp
00401137 pop ebp
00401138 ret
结果截图
程序中的栈结构如下图示:
__stdcall 规则由被调用者本身去调整堆栈,在汇编的角度上说,恢复堆栈的行为发生在调用者函数内,考虑这样一段代码(VC 下Debug):
#include <cstdio>
void __stdcall func(int param1, int param2, int param3) {
int var1 = param1;
int var2 = param2;
int var3 = param3;
printf("%ld\n", long(¶m1));
printf("%ld\n", long(¶m2));
printf("%ld\n", long(¶m3));
printf("----------------\n");
printf("%ld\n", long(&var1));
printf("%ld\n", long(&var2));
printf("%ld\n", long(&var3));
return ;
}
int main() {
func(1, 2, 3);
return 0;
}
注意到 func 函数使用了 __cdecl 进行修饰(其实不需要,因为 VC 下默认的是 __cdecl 规则, 这里是为了更为清晰),生成汇编代码如下:
1: #include <cstdio>
2:
3: void __stdcall func(int param1, int param2, int param3) {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,4Ch
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-4Ch]
0040102C mov ecx,13h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
4: int var1 = param1;
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
5: int var2 = param2;
0040103E mov ecx,dword ptr [ebp+0Ch]
00401041 mov dword ptr [ebp-8],ecx
6: int var3 = param3;
00401044 mov edx,dword ptr [ebp+10h]
00401047 mov dword ptr [ebp-0Ch],edx
.............................................. ; 省略 printf 代码
15: return ;
16: }
004010BD pop edi
004010BE pop esi
004010BF pop ebx
004010C0 add esp,4Ch
004010C3 cmp ebp,esp
004010C5 call __chkesp (004011d0)
004010CA mov esp,ebp
004010CC pop ebp
004010CD ret 0Ch ; __stdcall 在这里(被调用函数)恢复堆栈,但如果是 __cdecl 的话,这里是 ret,
; 堆栈的恢复由调用者(这里是 main)来负责
*******************************************************************************************************************
18: int main() {
........................................... ; 省略建立堆栈代码
19: func(1, 2, 3);
00401118 push 3 ; param3 压入堆栈
0040111A push 2 ; param2 压入堆栈
0040111C push 1 ; param1 压入堆栈
0040111E call @ILT+0(func) (00401005) ; @ILT+0(func) 是函数的修饰名,而 00401005 则是调用函数func的地址
20: return 0;
00401123 xor eax,eax
21: }
00401125 pop edi
00401126 pop esi
00401127 pop ebx
00401128 add esp,40h
0040112B cmp ebp,esp
0040112D call __chkesp (004011d0)
00401132 mov esp,ebp
00401134 pop ebp
00401135 ret
运行的结果与使用 __cdecl 规则的一样,两者的栈结构基本一致的,唯一的不同就是调整堆栈(恢复堆栈)的位置以及生成函数的修饰名不同,而这样的不同正是 __stdcall 和 __cdecl 最为重要的不同点