函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者 。
在参数传递中,有两个很重要的问题必须得到明确说明:
I 当参数个数多于一个时,按照什么顺序把参数压入堆栈;
II 函数调用后,由谁来把堆栈恢复原状 。
假如在C语言中,定义下面这样一个函数:
int func(int x,int y, int z)
然后传递实参给函数func()就可以使用了 。但是,在系统中,函数调用中参数的传递却是一门学问 。因为在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数 。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调 。为此,计算机用栈来支持参数传递 。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算 。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原状 。
在高级语言中,通过函数调用约定来说明参数的入栈和堆栈的恢复问题 。常见的调用约定有:
__stdcall__cdecl__fastcallthiscall__naked call
不同的调用规约,在参数的入栈顺序,堆栈的恢复,函数名字的命名上就会不同 。在编译后的代码量,程序执行效率上也会受到影响 。
在以上几种调用约定中,只有__cdecl是可以支持变长参数的(如支持printf()参数数量不确定的函数),由调用者负责堆栈管理,这两者是相关的,因为调用者负责传参,知道参数的数量和类型,如果要支持数量不确定的参数的话,只能是调用者来管理堆栈平衡 。__cdecl也是C语言默认的调用约定 。__stdcall每次函数调用都要由编译器产生还原堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多 。
文章插图
【C/C++|图文深入理解函数调用的5种约定】
示例代码:
#include <stdio.h>int __stdcall stdcallF(int x, int y);// 被调用函数自身修改堆栈,WINAPI和CALLBACK//intcdeclF(int x ,int y);// 默认的C调用约定int __cdecl cdeclF(int x,int y);// 明确指出C调用约定, 调用者修改堆栈,支持可变参数,比如printf()int __fastcall fastcallF(int x,int y,int z); // 被调用者修改堆栈int __declspec(naked) sub(int a,int b)// 编译器不会给这种函数增加初始化和清理代码,{// 也不能用return语句返回值,只能程序员控制,// 插入汇编返回结果 。因此它一般用于驱动程序设计__asm mov eax,a__asm sub eax,b__asm ret}class CAdd{int a,b;public:CAdd(int aa,int bb):a(aa),b(bb){}int add(int c);// 参数从右向左入栈,this指针最后入栈// 参数个数不确定时,调用者清理堆栈,否则函数自己清理堆栈};int main(){int a = 3;int b = 4;int c = 5;int s = 0;sub(3,4);s =stdcallF(a,b);s += cdeclF(a,b);s += fastcallF(a,b,c);CAdd cadd(3,4);s += cadd.add(c);printf("%dn",s); //38getchar();return 0;}int __stdcall stdcallF(int x, int y)// WINAPI和CALLBACK{// 被调用函数自身修改堆栈return x+y;}//intcdeclF(int x ,int y)// 默认的C调用约定int __cdecl cdeclF(int x,int y)// 明确指出C调用约定{// 调用者修改堆栈,支持可变参数,比如printf()return x+y;}int __fastcall fastcallF(int x,int y,int z)// 被调用者修改堆栈{// 函数的第一个和第二个参数通过ecx和edx传递,剩余参数从右到左入栈// 在X64平台,默认使用了fastcall调用约定,因其有较多的寄存器return x+y+z;}int CAdd::add(int c)// 参数从右向左入栈,this指针最后入栈{// 参数个数不确定时,调用者清理堆栈,否则函数自己清理堆栈return a+b+c;}
我们来看一下__cdecl调用的调用者的汇编:34:s += cdeclF(a,b);004010B0movedx,dword ptr [ebp-8]004010B3pushedx// 压参b004010B4moveax,dword ptr [ebp-4]004010B7pusheax// 压参a004010B8call@ILT+10(cdeclF) (0040100f)004010BDaddesp,8// 主调函数做堆栈平衡,2个int参数,esp偏移8字节004010C0movecx,dword ptr [ebp-10h]004010C3addecx,eax004010C5movdword ptr [ebp-10h],ecx// 返回值
__cdecl调用的被函数的汇编:49:int __cdecl cdeclF(int x,int y)// 明确指出C调用约定50:{// 调用者修改堆栈,支持可变参数,比如printf()00401230pushebp00401231movebp,esp00401233subesp,40h00401236pushebx00401237pushesi00401238pushedi00401239leaedi,[ebp-40h]0040123Cmovecx,10h00401241moveax,0CCCCCCCCh00401246rep stosdword ptr [edi]51:return x+y;00401248moveax,dword ptr [ebp+8]0040124Baddeax,dword ptr [ebp+0Ch]52:}0040124Epopedi0040124Fpopesi00401250popebx00401251movesp,ebp00401253popebp00401254ret// 这里被调函数没有做堆栈平衡
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- TCP和UDP的区别,深入理解TCP三次握手和四次挥手的全过程
- 纯代码解析 C++内存池的简单原理及实现
- 微软在Edge浏览器中更深入整合Office功能
- C++ struct和class的选择
- C++的逐层抽象:从结构体到类、模板
- 人教版七年级上册语文第10课再塑生命的人课件图文?七年级上册语文第10课再塑生命的人
- C++ 如何装饰函数实现代码最大程度复用
- C++ 如何允许程序中存在 BUG,却还能继续运行
- C/C++内存泄漏的原因、检测及解决方法?
- 做自媒体,我为什么放弃短视频,而选择了图文创作