C++ 的函数调用约定

  • 函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。
  • 在windows平台上的C++编程中经常会看到一些__stdcall, __cdecl, WINAPI, CALLBACK等等关键字在函数前面,在.NET中还有__clrcall, __thiscall等关键字,有时加不加它们都可以,但是有时必须加上,不然编译不过。本文要讨论的就是这些关键字:调用约定(Calling Convention),有时也叫做“函数调用约定”或者“调用规范”。本文采用MSDN的官方翻译:“调用约定”。
  • 那什么是调用约定呢?首先让我们看看一个函数调用到底需要经历哪几个过程,编译器到底为我们做了些什么。 
    ①把函数的参数压栈或者储存到寄存器
    ②跳转到函数
    ③把函数使用到的一些寄存器压栈
    ④执行函数
    ⑤处理函数返回值
    ⑥对于第3步中压栈的那些寄存器,恢复它们原来的值
    ⑦根据不同的调用约定,清除第1步中压栈的参数,然后返回,或者先返回然后清除。 
  • 可以看到第6步是第3步的逆操作,而第7步是第1,2步的逆操作,调用约定主要是定义了第1,7步骤中的规则:怎么去传递参数,谁负责去清除栈上的参数。
  • 调用约定跟堆栈清除密切相关。如果写一个汇编函数,给 C/C++ 调用,在 __cdecl 方式下,则汇编函数无需清除堆栈,在 __stdcall 方式下,汇编函数需要在返回(RET)之前恢复堆栈。
  • C 语言有: __cdecl、__stdcall、__fastcall、naked、__pascal。
  • C++ 语言有: __cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall,比 C 语言多出一种 __thiscall 调用方式。

1. __cdecl

__cdecl调用约定又称为 C 调用约定,是 C/C++ 语言缺省的调用约定。参数按照从右至左的方式入栈,函数本身不清理栈,此工作由调用者负责,返回值在EAX中。由于由调用者清理栈,所以允许可变参数函数存在,如int sprintf(char* buffer,const char* format,...);。

2. __stdcall

__stdcall 很多时候被称为 pascal 调用约定。pascal 语言是早期很常见的一种教学用计算机程序设计语言,其语法严谨。参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。

3. __fastcall

顾名思义,__fastcall 的特点就是快,因为它通过 CPU 寄存器来传递参数。他用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在 EAX 中。

4. naked

naked 是一个很少见的调用约定,一般不建议使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果,此调用约定必须跟 __declspec 同时使用。例如定义一个求和程序,如__declspec(naked) int  add(int a,int b);。

5. __pascal

这是 pascal 语言的调用约定,跟 __stdcall 一样,参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。VC 中已经废弃了这种调用方式,因此在写 VC 程序时,建议使用 __stdcall 代替。

6. __thiscall

这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。如果参数确定,this 指针存放于 ECX 寄存器,函数自身清理堆栈;如果参数不确定,this指针在所有参数入栈后再入栈,调用者清理栈。__thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。

参考文章:

调用约定

C/C++函数调用约定

猜你喜欢

转载自blog.csdn.net/weixin_39731083/article/details/82533349