【C++】4、函数调用约定

4、函数调用约定

4.1 什么是函数调用约定?

我们都知道C或者C++中,在调用函数的时候,函数的参数通过栈来传递。如果是一个参数的话非常好控制,但是如果是多个参数,就会遇到很多问题。比如:它们参数的传递顺序是什么样?它们在内存中是什么形式?栈帧是如何销毁的(调用者还是被调用者弹出)?

为了解决这些问题,引入了一个概念:函数调用约定。

4.2 常用的函数调用约定

常见的函数调用约定有这五种:__ stdcall、__ cdecl、__ fastcall、__ thiscall、__ naked call。

4.2.1 __ stdcall

stdcall是StandardCall的缩写,看见名字就知道这是C++的调用约定方式。这种方式规定所有的参数是从右向左依次入栈,如果是调用类成员方法的时候,需要将隐藏的this指针(对象的地址)作为最后一个参数入栈。当函数返回时,__ stacall采用自动清栈的方式,使用的指令是 retnX,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。由于是自动的清栈方式,必须在编译时期就确定好参数的个数。

4.2.2 __ cdecl

这个也很好区别,看见大写的c,就知道cdecl是C语言的调用约定方式。它是C语言缺省的调用约定。这种调用方式规定参数从右向左依次入栈。但在函数返回之后,需要手动清栈。由于是手动方式清栈,被调用函数就不会要求调用者传递参数的个数。这也就导致无论参数的形式,在编译时期都不会产生错误。

4.2.3 __fastcall

__ fastcall顾名思义,就是很快的函数调用约定。这种方式规定,将前两个参数(或者若干个)通过寄存器进行传递,剩下的参数还是通过入栈的方式传递。返回方式和 __ stdcall类似。

在X64的平台上,会默认使用__ fastcall调用约定。

1、前4个参数(在Linux 64上是6个寄存器RDI,RSI接着后面的寄存器)从左向右传入寄存器RCX、RDX、R8、R9中,后面的参数从右向左入栈。

2、浮点前4个参数传入XMM0,XMM1,XMM2,XMM3中,其它参数传递到堆栈中。

3、被调用函数的返回值是整数时,则返回值被存放于RAX;浮点数返回在XMM0中

4、RBX、RBP、R12 - R15被划分为被调用者保存寄存器,是使用前需要push的。

详细可以根据《深入理解计算机系统基础》第三章 3.7过程学习。

4.2.4 __ thiscall

__ thiscall也很好理解,这是传递C++类成员方法参数的调用约定(传递this指针)。当使用对象 .函数名 调用 成员方法时,会先将对象的地址传递给ecx寄存(VC中),到达成员函数内部的时候,将ecx存储的值赋给this指针。

详情可见 《1、面向对象和面向过程常见问题》中的问题10

4.3常见的函数名修饰约定

在C和C++中函数在内部并不是只通过函数名来标识的,而是通过修饰名来识别的,否则C++无法拥有多态的特性。修饰名是在编译时期生成的。

4.3.1 C语言修饰名约定

函数名转换修饰名字,大小写不改变为前提。

4.3.1.1__ stdcall 名字修饰:

​ 在函数名前加一个下划线,在函数名后加 "@"和参数的字节数。如同:_functionname@number。

4.3.1.2 __ cdecl 名字修饰:

​ 在函数名前加下划线,如同:_functionname

4.3.1.3 __ fastcall 名字修饰:

​ 在函数名前加 “@”,后面和__ stdcall一样,在函数名后加 ”@“ 和参数的字节数。如同:

@functionname@number 。

4.3.2 C++修饰名约定

4.3.2.1 __ stdcall 名字修饰:

​ 在函数名前加一个 ?,在函数名后加 "@@YG"作为参数开始的标志,后面跟参数表代号,如果代号出现多次,使用 ‘0’ 代替。最后结束以 ”@Z“ 来标识。

格式如下:?functionname@@YG 参数表代号 @Z。

例子:int Test1(char *var1,unsigned long) -----“?Test1@@YGHPADK@Z”

参数表代号如下:

X——void,

D——char,

E——unsigned char,

F——short,

H——int,

I——unsigned int,

J——long,

K——unsigned long,

M——float,

N——double,

_N——bool,

4.3.2.2 __ cdecl 名字修饰

​ VC++对函数默认的声明是" __cedcl "。

​ 首先在函数名前加一个 ’ ? ', 函数名后是"@@YA",表示参数开始标志,后面跟参数表代 号,如果参数出现多次,使用 ‘0’ 代替,最后以 “@Z” 结束。

​ 格式如下:?functionname@@YA 参数表代号 @Z。

​ 例子:int Test1(char *var1,unsigned long) -----“?Test1@@YAHPADK@Z”

4.3.2.3 __ fastcall 名字修饰

​ 规则同上面的*_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"*。

​ 格式如下:?functionname@@YI参数表代号 @Z。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值