分享

函数调用约定

 TUBOSS 2014-09-30

函数调用约定包括传递参数的顺序,谁负责清理参数占用的堆栈等,例如 :

  参数传递顺序 谁负责清理参数占用的堆栈
__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. 速度不同: _cdecl更快(代码更多当然意味着运行更快,有点像内联函数)

e. 谁负责恢复堆栈:_cdecl主调用函数进行参数压栈并且恢复堆栈;_stdcall主调用函数进行参数压栈,被调函数恢复堆栈;这也正是产生
   a). 不同代码大小的原因:如果使用_cdecl的函数多次调用同一函数,就要产生多份恢复码。
   b). 功能不同的原因:实现变长参数列表。一份恢复码只能将一种长度的参数表出栈,所以要对不同长度的参数表堆栈恢复,必须要有多份代码,所以变长参数必须有主调函数恢复(所以是_cdecl)。

f. 产生的函数名不同:

_stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。_cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。

g. 使用范围:

_stdcall:通常用于DLL的创建(以支持多语言调用);此外Win32 API函数皆用_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,例如:

int __stdcall add(int a, int b);

在delphi中将这个函数也声明为__stdcall,就可以调用了:

function add(a: Integer; b: Integer): Integer;
stdcall; external ‘a.dll’;

因为考虑到可能被其它语言的程序调用,不少API采用__stdcall的调用约定。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多