首页 > 其他分享 >函数调用约定

函数调用约定

时间:2023-01-08 00:11:23浏览次数:35  
标签:__ 函数 约定 函数调用 参数 修饰

函数调用约定

一、什么是函数调用约定?

我们都知道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

相关文章