函数调用约定
对于函数调用约定,我们首先应该知道的是为什么产生函数调用约定?为什么要用函数约定?
首先,我们在写函数时,我们可以知道的是
int func(int a,int b)
{
.........
}
像这样,我们很容易知道,函数的返回值是什么类型,有几个参数,以及参数是什么类型。对于我们的CPU来说,我们的C语言是先编译成机械码之后,我们CPU才能执行,但是,变成一条一条的机械码之后,我们的CPU怎么才能知道这个函数有几个参数,以及什么类型等等的问题呢?这个时候就需要我们的函数调用约定。
其次,深入函数的调用,对于内存上来说,函数的调用者首先依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中申请空间并取得数据,然后在这个临时开辟的空间中进行计算。函数计算结束以后,调用者或者函数本身就会将这片临时的空间释放,然后将参数出栈,使堆栈恢复原状。
因此自然而然的在进行函数调用时,有两个很重要的问题需要得到明确说明:
1、当参数个数多于一个时,按照什么顺序将参数压入堆栈
2、函数调用后,由谁来将堆栈恢复原状
因此,我们的相对高级的语言在处理上述的这些问题的时候,就有着一些约定。
所以函数调用约定到底是什么呢?函数调用约定就是:函数调用者和被调用的函数体之间关于参数传递、返回值传递、堆栈清除、寄存器使用的一种约定。
一些常见的函数调用约定以及使用
我们一般写函数的格式都是这样的
int func(int a,int b)
{
.........
}
其实这样写是省略了我们函数调用约定的,这样写是运用了c默认的函数调用约定__cdecl,如果不省略我们的函数调用约定就应该这样写
int __cdecl func(int a,int b)
{
.........
}
在我们函数名前面加上我们的函数调用约定。
__cdecl(C/C++默认的调用约定)
__cdecl是C Declaration的缩写,表示C\C++默认的函数调用约定。当我们没有写函数调用约定时,我们编译器会自己默认将其加上。
_cdecl规定:
- 参数从右向左压入堆栈
- 由调用者负责清理堆栈(传送参数的内存栈是由调用者来维护的,返回值在EAX中)
- C调用约定允许函数的参数的个数是不固定的。(因此调用参数个数可变的函数只能采用这种方式(如printf))
- 编译器在编译时会在函数名前加上一个下划线前缀生成修饰名,格式为_functionname。如函数int Add(int a, int b)的修饰名是_Add
__stdcall(Windows的大多数的API函数采用了这种调用方式)
- 参数从右向左压入堆栈
- 由被调用者负责清理堆栈(函数自己在退出时清空堆栈,返回值在EAX中)
- __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_function@number。如函数int sub(int a, int b)的修饰名是_sub@8。
这里__stdcall和__cdecl中第二条主要的区别在于,当调用者负责清理堆栈的时候,调用一次就会需要在调用处编译出恢复栈顶的代码,而被调用者负责清理堆栈的时候,在函数内部编译出恢复栈顶的代码一份就行了。这样就会节省我们的空间。
__fastcall(用于一些需要对参数快速处理的函数)
正如其名,_fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数。
- 函数的第一个和第二个(从左向右)两个DWORD或更小的参数,通过ecx和edx传递(寄存器传递),其他参数通过桟传递。从第三个参数(如果有的话)开始从右向左的顺序压栈;
- 函数自身清理堆栈
- 在函数名之前加上"@",在函数名后面也加上“@”和参数字节数,例如@function@8
和__stdcall与__cdecl相比最主要的区别是,__fastcall把第1和第2个参数放到了寄存器中,而不是压栈,因为寄存器的读写速度比栈快很多,所以叫做fast call。
__thiscall(C++类成员函数默认调用约定)
thiscall是一个不能明确指明的函数修饰,thiscall不是关键字,thiscall只能用于C++类成员函数的调用。而且thiscall也是C++成员函数缺省的调用约定,由于成员函数调用还有一个this指针,因此需要特殊处理。
- 参数从右向左压入堆栈
- 如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。如果参数个数确定,this指针通过ecx传递给被调用者
- 如果参数个数不确定,调用者清理堆栈,否则函数自己清理堆栈
补充
C++编译时函数名修饰约定规则:
以“?”标识函数名的开始,后跟函数名;
函数名后面以“@@YG”标识参数表的开始,后跟参数表;
2.函数名后面标识调用约定,然后跟参数列表。
__cdecl,@@YA
__stdcall,@@YG
__fastcall,@@YI
参数表以代号表示:
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,
PA–表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复
…
参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。
例如:
int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
“int __cdecl a(char)————?a@@YAHD@Z”
参考
C语言的函数调用约定(stdcall+cdecl+thiscall+fastcall)_int stdcall_莲娃的博客-CSDN博客
函数调用约定的详解_YoungYangD的博客-CSDN博客
带你玩转Visual Studio——调用约定__cdecl、__stdcall和__fastcall_vs调用规则cdecl_luoweifu的博客-CSDN博客
调用约定(Calling Conventions) 函数名修饰(Name Mangling) 二进制分析工具 - 通用C++ - 蜗牛大锅的博客 (3scard.com)