C语言三种函数调用约定_cdecl与_stdcall及_fastcall详细讲解
目录
- C语言常用的调用约定
- 一、_cdecl调用约定
- 二、_stdcall调用约定
- 三、_fastcall调用约定
- 总结
C语言常用的调用约定
以下就是C语言常用的三种调用约定:
调用约定 | 参数压栈顺序 | 平衡堆栈 |
---|---|---|
__cdecl | 从右往左依次入栈 | 调用者清理堆栈 |
__stdcall | 从右往左依次入栈 | 自身清理堆栈 |
__fastcall | ECX/EDX传递前两个参数 剩下的从右往左依次入栈 | 自身清理堆栈 |
下面会举例为大家讲解三种调用约定的区别。
一、_cdecl调用约定
这是C语言默认的调用约定,使用的平栈方式为外平栈
示例代码:
以下代码不使用任何调用约定,让我们来看看函数默认的调用约定是什么。
#include <stdio.h> int method(int x,int y) { return x+y; } int main() { __asm mov eax,eax; // 此处设置断点 method(1,2); return 0; }
编译、调试、ALT+8调出反汇编如下:
根据上面这张图的描述,默认的约定很符合__cdecl约定。
使用cdecl约定,如下:
vs2010:Ctrl+Alt+F7重新生成、F5调试、ALT+8查看反汇编:
一模一样,可以看出__cdecl就是C语言默认的调用约定。
二、_stdcall调用约定
和__cdecl一样都是从右往左入栈参数,不过该调用约定使用的平栈方式是内平栈
示例代码:
Ctrl+Alt+F7重新生成、F5调试、ALT+8查看反汇编:
可以看到,这里已经看不到堆栈的处理了。
F11不断执行,直到进入call指令调用的method函数中:
平栈操作跑到函数内部了,__cdecl约定是调用者(main)函数进行平栈,而__stdcall约定是函数内部自身进行平栈。
三、_fastcall调用约定
这是一个比较特殊的调用约定,当函数参数为两个或者以下时,该约定的效率远远大于上面两种,当然随着参数越来越多,该约定与上面两种约定的差距逐渐缩小。
证明如下:
首先,我们使用__fastcall调用约定并传入两个参数。
重新生成、调试、汇编:
F11进入函数内部查看:
可以看出函数内部和外部都没有清理堆栈的操作。
这也就是__fastcall效率高的原因。
因为寄存器就是属于cpu的,然后堆栈是内存,使用cpu进行操作的效率肯定会大于使用内存,所以我们使用寄存器的效率肯定比push传参效率高很多啊。
那么为什么没有平栈操作呢?
因为我们没有使用堆栈啊,我们只是用了寄存器,并没有使用堆栈操作。
但是当我们传入更多的参数的时候就需要用到堆栈了,因为__fastcall他只给我们提供了两个寄存器ECX/EDX可以用来传参。
四个参数试试:
重新生成、调试、汇编:
F11进入函数内部查看:
通过四个参数的传递,证明了:
函数参数除了前两个参数使用寄存器、其他的依旧使用堆栈从右往左传参,并且是自身清理堆栈,不是调用者清理。
思考为什么参数越来越多的时候,__fastcall与其他调用约定的差距越来越小呢?
答:首先我们知道了使用寄存器(cpu)的效率远远大于使用堆栈(内存),然而__fastcall约定也只能使用两个寄存器,当函数参数只有两个时,__fastcall可以完全使用寄存器进行函数传参,所以这个时候他和__cdecl和__stdcall的差距最大。随着参数越来越多,__fastcall依旧只能使用两个寄存器,这样一来参数越多,__fastcall使用内存的占比就越大,所以性能差距也就越来越小。
总结
以上的内容汇总如下:
调用约定 | 参数压栈顺序 | 平衡堆栈 | 调用约定特点 |
---|---|---|---|
__cdecl | 从右往左依次入栈 | 调用者清理堆栈 | 这是C语言默认的调用约定,使用的平栈方式为外平栈 |
__stdcall | 从右往左依次入栈 | 自身清理堆栈 | 和__cdecl一样都是从右往左入栈参数,不过该调用约定使用的平栈方式是内平栈 |
__fastcall | ECX/EDX传递前两个参数 剩下的从右往左依次入栈 | 自身清理堆栈 | 这是一个比较特殊的调用约定,当函数参数为两个或者以下时,该约定的效率远远大于上面两种,当然随着参数越来越多,该约定与上面两种约定的差距逐渐缩小。 |
到此这篇关于C语言三种函数调用约定_cdecl与_stdcall及_fastcall详细讲解的文章就介绍到这了,更多相关C语言函数调用约定内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!