函数调用约定

更新时间:2022-08-26 10:48

函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。

几种类型

__stdcall__cdecl__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall

约束事件

参数传递顺序

1.从右到左依次入栈:__stdcall__cdecl,__thiscall,__fastcall

2.从左到右依次入栈:__pascal

调用堆栈清理

1.调用者清除栈。

2.被调用函数返回后清除栈。

常用描述

__cdecl

1、参数是从右向左传递的,也是放在堆栈中。

2、堆栈平衡是由调用函数来执行的(在call B,之后会有add esp x,x表示参数的字节数)。

3、函数的前面会加一个前缀_(_sumExample)

下面来看看具体的反汇编代码,这是从VC反汇编的代码截取的一部分代码。

从上面的两个push操作我们就可以知道参数是从右向左传递的了。另外这里也回答前面的问题为什么参数会被扩展为4个字节,因为堆栈的操作都是对一个字进行操作的,所以参数都是4个字节的。

这里就是调用函数操作了。在进行call操作之后,会自动将call的下一条语句作为函数的返回地址保存在栈中,也就是下面的(00401098)。在地址00401014处我们可以看到这样的一小段代码

这里就可以知道程序在编译之后会在函数前面加上前缀_

这里就是平衡堆栈操作了。可以看出是在调用者进行的。

保存值是由eax寄存器返回的,从这里就可以看出了。

这上面的一段代码就是函数的开端了。也就是function prolog。通过将一些寄存器来对它们进行保存,也就像中断发生后,需要保护现场一样。

这里就是函数收尾,也就是function epilog

经过上面的分析,相信你对__cdecl调用约定有了比较清晰的认识了。但是这里我们应该想想为什么不在被调函数内进行堆栈平衡呢?在这里我们应该要考虑类似于像scanfprintf这样的函数,这里我们应该明白这两个函数的参数都是可变的,如果参数不固定的话,在被调用函数内就无法知道参数究竟使用了多少个字节,所以为了实现可变参数,我们必须要在被调函数执行之后我们才知道参数究竟用了多少字节,所以我们在调用者来进行堆栈平衡操作。在后面我们将要对printf函数内部是怎么实现做一些探究。

__stdcall

Win32 API函数绝大部分都是采用__stdcall调用约定的。WINAPI其实也只是__stdcall的一个别名而已。

还是与上面一样,我们在函数的面前用__stdcall作为修饰符。此时函数将会采用__stdcall调用约定

__stdcall调用约定的主要特征是:

1、参数是从右往左传递的,也是放在堆栈中。

2、函数的堆栈平衡操作是由被调用函数执行的。

3、在函数名的前面用下划线修饰,在函数名的后面由@来修饰并加上需要的字节数的空间(_sumExample@8)。

main函数

这两个push可以说明函数的参数是由右向左传递的。

再来看看函数的代码。

函数的开端与__cdecl调用约定是相同的

函数的收尾也是和__cdecl调用约定是相同的

另外在最后面将对堆栈进行平衡操作。

ret 8 //两个4字节的参数

上面的是文章本来的说明,但在VC中却好像有点区别。

main函数

sumExample函数

因为栈的清理(堆栈平衡操作)是由被调用函数执行的。所以使用__stdcall调用约定生成的可执行文件要比__cdecl的要小,因为在每次的函数调用都要产生堆栈清理的代码。函数具有可变参数像我wsprintf这个函数,与前面的prinf一样,都必须使用__cdecl调用约定,因为只有调用者才知道参数的数量在每一次的函数调用,因此也只有调用者才能够执行堆栈清理操作。

__fastcall

__fastcall见名知其意,其特点就是快。__fastcall函数调用约定表明了参数应该放在寄存器中,而不是在栈中,VC编译器采用调用约定传递参数时,最左边的两个不大于4个字节(DWORD)的参数分别放在ecx和edx寄存器。当寄存器用完的时候,其余参数仍然从右到左的顺序压入堆栈。像浮点值、远指针和__int64类型总是通过堆栈来传递的。

下面来看看使用测试的源代码

免责声明
隐私政策
用户协议
目录 22
0{{catalogNumber[index]}}. {{item.title}}
{{item.title}}