函数调用约定包括传递参数的顺序,谁负责清理参数占用的堆栈等,例如 :
| 参数传递顺序 | 谁负责清理参数占用的堆栈 |
__pascal | 从左到右 | 调用者 |
__stdcall | 从右到左 | 被调函数 |
__cdecl | 从右到左 | 调用者 |
调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。在Windows上,__cdecl是C/C++程序的缺省函数调用约定。
在有的cpu上,编译器会用寄存器传递参数,函数使用的堆栈由被调函数分配和释放。这种调用约定在行为上和__cdecl有一个共同点:实参和形参数目不符不会导致堆栈错误。
不过,即使用寄存器传递参数,编译器在进入函数时,还是会将寄存器里的参数存入堆栈指定位置。参数和局部变量一样应该在堆栈中有一席之地。参数可以被理解为由调用函数指定初值的局部变量。
_stdcall与_cdecl的不同
a.默认支持:VC默认使用_cdecl。所以如果需要使用_stdcall,可采用两种方法:(1)可以在函数名前手工添加,只对单一函数有效(2)直接修改工程属性(C/C++ > Advanced >Calling Convention)来一次性配置所有的函数
b. 功能不同:_cdecl可实现变长参数列表
c. 代码大小:_stdcall更小
d.速度不同:
e.谁负责恢复堆栈:_cdecl主调用函数进行参数压栈并且恢复堆栈;_stdcall主调用函数进行参数压栈,被调函数恢复堆栈;这也正是产生
f. 产生的函数名不同:
_stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。_cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
g. 使用范围:
_stdcall:通常用于DLL的创建(以支持多语言调用);此外Win32API函数皆用_stdcall(比如MessageBox),所以Win32程序中的自定义函数也做好使用_stdcall。
_cdecl:非DLL的console程序。
假设函数A是__stdcall,函数B调用函数A。你必须通过函数声明告诉编译器,函数A是__stdcall。编译器自然会产生正确的调用代码。
如果函数A是__stdcall。但在引用函数A的地方,你却告诉编译器,函数A是__cdecl方式,编译器产生__cdecl方式的代码,与函数A的调用约定不一致,就会发生错误。
以delphi调用VC函数为例,delphi的函数缺省采用__pascal约定,VC的函数缺省采用__cdecl约定。我们一般将VC的函数设为__stdcall,例如:
在delphi中将这个函数也声明为__stdcall,就可以调用了:
stdcall; external ‘a.dll’;
因为考虑到可能被其它语言的程序调用,不少API采用__stdcall的调用约定。