函数调用约定

49 篇文章 2 订阅

函数的调用约定不仅决定着函数参数压入栈中的先后顺序,还决定了应该由谁来释放给被调函数传递的参数所占用的栈空间。

常见的约定:

__stdcall__cdecl__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall

参数传递顺序

1.从右到左依次入栈:__stdcall__cdecl,__thiscall,__fastcall

2.从左到右依次入栈:__pascal

调用堆栈清理

1.调用者清除栈。

2.被调用函数返回后清除栈。

VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调用方式,在 DLL 导出函数中,为了跟 Windows API 保持一致,建议使用 __stdcall 方式。

 在VC中,可以设置默认的调用约定,设置路径为:

Project -> Properties -> Configuration Properties -> C/C++ -> Advanced  -> Call Conversion

__cdecl

1、参数是从右向左传递的,也是放在堆栈中。

2、堆栈平衡是由调用函数来执行的(在call B,之后会有add esp x,x表示参数的字节数)。

3、函数的前面会加一个前缀_(_sumExample)

int __cdecl Add2(int x1, int x2)

{

     return x1 + x2;

}

因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大

__stdcall

Win32 API函数绝大部分都是采用__stdcall调用约定的。WINAPI其实也只是__stdcall的一个别名而已。

1

#define WINAPI __stdcall

还是与上面一样,我们在函数的面前用__stdcall作为修饰符。此时函数将会采用__stdcall调用约定

1

int __stdcall sumExample (int a, int b);

__stdcall调用约定的主要特征是:

1、参数是从右往左传递的,也是放在堆栈中。

2、函数的堆栈平衡操作是由被调用函数执行的。

3.3)函数修饰名约定:VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上”@”和参数的字节数。
ex. VC: int f(void *p) (编译后)-> _f@4(在外部汇编语言里可以用这个名字引用这个函数)

int _stdcall Add1(int x1, int x2)

{

     return x1 + x2;

}

因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小

__cdecl:    C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。

__stdcall:            windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。

__fastcall:   快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。因为栈是位于内存的区域,而寄存器位于CPU内,故存取方式快于内存,故其名曰“__fastcall”。

大多数API都采用__stdcall调用规范,这是因为几乎所有的语言都支持__stdcall调用.
相比之下,__cdecl只有在C语言中才能用. 但__cdecl调用有一个特点,就是能够实现可变参数的函数调用,
比如printf,这用__stdcall调用是不可能的.

__declspec主要是用于说明DLL的引出函数的,在某些情况下用__declspec(dllexport)在DLL中声明引出函数.

#define SIMPLEDLL_EXPORT

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif

导出函数: 

__declspec(dllexport)  void  Function1(void);

__declspec(dllexport)  void  __stdcall Function1(void);

__declspec(dllexport)  void  __cdecl  Function1(void);

导出类:class  __declspec(dllexport)  CExampleExport : public CObject

使用MFC提供的修饰符号_declspec(dllexport)

在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。__declspec
(dllexport)在C调用约定、C编译情况下可以去掉输出函数名的下划线前缀。extern “C”使得在C++中
使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern “C”关键词。用extern “C”来
指明该函数使用C编译方式。输出的“C”函数可以从“C”代码里调用。

__ declspec(dllexport):
将一个函数声名为导出函数,就是说这个函数要被其他程序调用,即作为DLL的一个对外函数接口。通常它和extern“C”合用,

例如,在一个C++文件中,有如下函数:
extern “C” {void __declspec(dllexport) __cdecl FUNCTION(int a,int b);}
其输出函数名为:Test

这是由于在制作DLL导出函数时由于C ++存在函数重载,因此__declspec(dllexport)FUNCTION(int,int)在DLL会被装饰,例如被装饰成为function_int_int,而且不同的编译器decorate的方法不同,造成了在用GetProcAddress的的取得FUNCTION地址时的不便,使用外部的“C”时,上述的装饰不会发生,因为ç没有函数重载,如此一来被外部的“C”修饰的函数,就不具备重载能力.

C中函数编译后命名会在函数名前加以"_",比如add函数编译成obj文件时的实际命名为_add,而c++命名则不同,为了实现函数重载同样的函数名add因参数的不同会被编译成不同的名字 


      即: 我们常用的stdcall标准调用约定和C调用约定,如果被调用函数是stdcall标准约定,则由被调函数释放传给被调函数的参数的栈空间。如果被调函数是C调用,则由主调函数去释放栈空间。

      比如我们经常用到的C函数printf,设定的是C调用约定,由主调函数进行释放。

      这个问题在设置回调函数时比较常见,特别是跨语言设置回调函数时。因为调用约定的不一致,可能会导致参数栈空间多释放了一次。比如C++的SDK给C#程序调用,因为C++语言中默认使用C调用约定,C#默认使用标准调用,如果回调函数没有显式地指明调用约定,在实际使用时就会出问题。

       默认情况下,Visual Studio 的/RTC编译选项只在Debug下是开启的,Release下该选项是关闭的。有的模块为了方便排查问题,在Release版本中开启了该编译选项。开启该选项会向代码添加很多额外的跟踪代码,会对程序的执行效率产生一定的影响。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值