函数调用约定
一、什么是函数调用约定?
我们都知道C或者C++中,在调用函数的时候,函数的参数通过栈来传递。如果是一个参数的话非常好控制,但是如果是多个参数,就会遇到很多问题。比如:它们参数的传递顺序是什么样?它们在内存中是什么形式?栈帧是如何销毁的(调用者还是被调用者弹出)?
为了解决这些问题,引入了一个概念:函数调用约定。
二、常用的函数调用约定
常见的函数调用约定有这五种:__ stdcall、__ cdecl、__ fastcall、__ thiscall、__ naked call。
2.1 __ stdcall
stdcall是StandardCall的缩写,看见名字就知道这是C++的调用约定方式。这种方式规定所有的参数是从右向左依次入栈,如果是调用类成员方法的时候,需要将隐藏的this指针(对象的地址)作为最后一个参数入栈。当函数返回时,__ stacall采用自动清栈的方式,使用的指令是 retnX,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。由于是自动的清栈方式,必须在编译时期就确定好参数的个数。
2.2 __ cdecl
这个也很好区别,看见大写的c,就知道cdecl是C语言的调用约定方式。它是C语言缺省的调用约定。这种调用方式规定参数从右向左依次入栈。但在函数返回之后,需要手动清栈。由于是手动方式清栈,被调用函数就不会要求调用者传递参数的个数。这也就导致无论参数的形式,在编译时期都不会产生错误。
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过程学习。
2.4 __ thiscall
__ thiscall也很好理解,这是传递C++类成员方法参数的调用约定(传递this指针)。当使用对象 .函数名 调用 成员方法时,会先将对象的地址传递给ecx寄存(VC中),到达成员函数内部的时候,将ecx存储的值赋给this指针。
三、常见的函数名修饰约定
在C和C++中函数在内部并不是只通过函数名来标识的,而是通过修饰名来识别的,否则C++无法拥有多态的特性。修饰名是在编译时期生成的。
3.1 C语言修饰名约定
函数名转换修饰名字,大小写不改变为前提。
3.1.1__ stdcall 名字修饰:
在函数名前加一个下划线,在函数名后加 "@"和参数的字节数。如同:_functionname@number。
3.1.2 __ cdecl 名字修饰:
在函数名前加下划线,如同:_functionname
3.1.3 __ fastcall 名字修饰:
在函数名前加 "@",后面和__ stdcall一样,在函数名后加 ”@“ 和参数的字节数。如同:
@functionname@number 。
3.2 C++修饰名约定
3.2.1 __ stdcall 名字修饰:
在函数名前加一个 ?,在函数名后加 "@@YG"作为参数开始的标志,后面跟参数表代号,
如果代号出现多次,使用 '0' 代替。最后结束以 ”@Z“ 来标识。
格式如下:?functionname@@YG 参数表代号 @Z。
例子:int Test1(char var1,unsigned long) -----“?Test1@@YGHDI@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,
3.2.2 __ cdecl 名字修饰
VC++对函数默认的声明是" __cedcl "。
首先在函数名前加一个 ' ? ', 函数名后是"@@YA",
表示参数开始标志,后面跟参数表代号,
如果参数出现多次,使用 '0' 代替,最后以 "@Z" 结束。
格式如下:?functionname@@YA 参数表代号 @Z。
例子:int Test1(char \*var1,unsigned long) -----“?Test1@@YAHPADK@Z”
3.2.3 __ fastcall 名字修饰
规则同上面的*_stdcall*调用约定,只是参数表的开始标识由上面的*"@@YG"*变为*"@@YI"*。
格式如下:?functionname@@YI参数表代号 @Z。
标签:__,函数,约定,函数调用,参数,修饰
From: https://www.cnblogs.com/baobaobashi/p/17029297.html