更新时间:2022-08-26 10:48
__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal,__vectorcall
从上面的两个push操作我们就可以知道参数是从右向左传递的了。另外这里也回答前面的问题为什么参数会被扩展为4个字节,因为堆栈的操作都是对一个字进行操作的,所以参数都是4个字节的。
这里就是调用函数操作了。在进行call操作之后,会自动将call的下一条语句作为函数的返回地址保存在栈中,也就是下面的(00401098)。在地址00401014处我们可以看到这样的一小段代码
这里就可以知道程序在编译之后会在函数前面加上前缀_
这里就是平衡堆栈操作了。可以看出是在调用者进行的。
保存值是由eax寄存器返回的,从这里就可以看出了。
这上面的一段代码就是函数的开端了。也就是function prolog。通过将一些寄存器来对它们进行保存,也就像中断发生后,需要保护现场一样。
这里就是函数收尾,也就是function epilog
经过上面的分析,相信你对__cdecl调用约定有了比较清晰的认识了。但是这里我们应该想想为什么不在被调函数内进行堆栈平衡呢?在这里我们应该要考虑类似于像scanf和printf这样的函数,这里我们应该明白这两个函数的参数都是可变的,如果参数不固定的话,在被调用函数内就无法知道参数究竟使用了多少个字节,所以为了实现可变参数,我们必须要在被调函数执行之后我们才知道参数究竟用了多少字节,所以我们在调用者来进行堆栈平衡操作。在后面我们将要对printf函数内部是怎么实现做一些探究。
__stdcall
Win32 API函数绝大部分都是采用__stdcall调用约定的。WINAPI其实也只是__stdcall的一个别名而已。
还是与上面一样,我们在函数的面前用__stdcall作为修饰符。此时函数将会采用__stdcall调用约定
__stdcall调用约定的主要特征是:
1、参数是从右往左传递的,也是放在堆栈中。
2、函数的堆栈平衡操作是由被调用函数执行的。
3、在函数名的前面用下划线修饰,在函数名的后面由@来修饰并加上栈需要的字节数的空间(_sumExample@8)。
这两个push可以说明函数的参数是由右向左传递的。
再来看看函数的代码。
函数的开端与__cdecl调用约定是相同的
函数的收尾也是和__cdecl调用约定是相同的
另外在最后面将对堆栈进行平衡操作。
ret 8 //两个4字节的参数
上面的是文章本来的说明,但在VC中却好像有点区别。
sumExample函数
因为栈的清理(堆栈平衡操作)是由被调用函数执行的。所以使用__stdcall调用约定生成的可执行文件要比__cdecl的要小,因为在每次的函数调用都要产生堆栈清理的代码。函数具有可变参数像我wsprintf这个函数,与前面的prinf一样,都必须使用__cdecl调用约定,因为只有调用者才知道参数的数量在每一次的函数调用,因此也只有调用者才能够执行堆栈清理操作。
__fastcall
__fastcall见名知其意,其特点就是快。__fastcall函数调用约定表明了参数应该放在寄存器中,而不是在栈中,VC编译器采用调用约定传递参数时,最左边的两个不大于4个字节(DWORD)的参数分别放在ecx和edx寄存器。当寄存器用完的时候,其余参数仍然从右到左的顺序压入堆栈。像浮点值、远指针和__int64类型总是通过堆栈来传递的。
下面来看看使用测试的源代码