您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

C/C++|图文深入理解函数调用的5种约定

时间:2021-11-30 10:42:18  来源:  作者:小智雅汇

函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。

在参数传递中,有两个很重要的问题必须得到明确说明:

I 当参数个数多于一个时,按照什么顺序把参数压入堆栈;

II 函数调用后,由谁来把堆栈恢复原状。

假如在C语言中,定义下面这样一个函数:

int func(int x,int y, int z)

然后传递实参给函数func()就可以使用了。但是,在系统中,函数调用中参数的传递却是一门学问。因为在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机用栈来支持参数传递。

函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原状。

在高级语言中,通过函数调用约定来说明参数的入栈和堆栈的恢复问题。常见的调用约定有:

__stdcall
__cdecl
__fastcall
thiscall
__naked call

不同的调用规约,在参数的入栈顺序,堆栈的恢复,函数名字的命名上就会不同。在编译后的代码量,程序执行效率上也会受到影响。

在以上几种调用约定中,只有__cdecl是可以支持变长参数的(如支持printf()参数数量不确定的函数),由调用者负责堆栈管理,这两者是相关的,因为调用者负责传参,知道参数的数量和类型,如果要支持数量不确定的参数的话,只能是调用者来管理堆栈平衡。__cdecl也是C语言默认的调用约定。__stdcall每次函数调用都要由编译器产生还原堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多。

C/C++|图文深入理解函数调用的5种约定

 

示例代码:

#include <stdio.h>

int __stdcall stdcallF(int x, int y);    // 被调用函数自身修改堆栈,WINAPI和CALLBACK

//int       cdeclF(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); //38
    
    getchar();
    return 0;
}
int __stdcall stdcallF(int x, int y)    // WINAPI和CALLBACK
{                                       // 被调用函数自身修改堆栈
    return x+y;
}

//int       cdeclF(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);
004010B0   mov         edx,dword ptr [ebp-8]
004010B3   push        edx   // 压参b
004010B4   mov         eax,dword ptr [ebp-4]
004010B7   push        eax  // 压参a
004010B8   call        @ILT+10(cdeclF) (0040100f)
004010BD   add         esp,8    // 主调函数做堆栈平衡,2个int参数,esp偏移8字节
004010C0   mov         ecx,dword ptr [ebp-10h]
004010C3   add         ecx,eax
004010C5   mov         dword ptr [ebp-10h],ecx   // 返回值

__cdecl调用的被函数的汇编:

49:   int __cdecl cdeclF(int x,int y)         // 明确指出C调用约定
50:   {                                       // 调用者修改堆栈,支持可变参数,比如printf()
00401230   push        ebp
00401231   mov         ebp,esp
00401233   sub         esp,40h
00401236   push        ebx
00401237   push        esi
00401238   push        edi
00401239   lea         edi,[ebp-40h]
0040123C   mov         ecx,10h
00401241   mov         eax,0CCCCCCCCh
00401246   rep stos    dword ptr [edi]
51:       return x+y;
00401248   mov         eax,dword ptr [ebp+8]
0040124B   add         eax,dword ptr [ebp+0Ch]
52:   }
0040124E   pop         edi
0040124F   pop         esi
00401250   pop         ebx
00401251   mov         esp,ebp
00401253   pop         ebp
00401254   ret    // 这里被调函数没有做堆栈平衡

__cdecl代表性的栈示意图:

C/C++|图文深入理解函数调用的5种约定

 

__cdecl代表性的函数栈帧的示例代码:

#include <stdio.h>

struct ST{
    int a;
    double d;
    //ST(int aa,double dd):a(aa),d(dd){};
};

ST test(double d,int a)
{
    char ch = 'a';
    char chs[5] = "";
    ST st;
    st.a = a;
    st.d = d;
    return st;
}

int main()
{
    ST s = test(2.3,4);
    printf("%d %fn",s.a,s.d);
    getchar();
    return 0;
}

ref:

http://mallocfree.com/basic/c/c-6-function.htm#79

-End-



Tags:函数   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
所谓内置函数,就是Python提供的, 可以直接拿来直接用的函数,比如大家熟悉的print,range、input等,也有不是很熟,但是很重要的,如enumerate、zip、join等,Python内置的这些函数非常...【详细内容】
2021-12-21  Tags: 函数  点击:(5)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  Tags: 函数  点击:(19)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  Tags: 函数  点击:(23)  评论:(0)  加入收藏
内置函数就是Python给你提供的,拿来直接用的函数,比如print.,input等。截止到python版本3.6.2 ,python一共提供了68个内置函数,具体如下...【详细内容】
2021-12-07  Tags: 函数  点击:(25)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  Tags: 函数  点击:(19)  评论:(0)  加入收藏
public class LambdaDemo { public static void main(String[] args) { /** * 用来判定true或者false boolean test(T t); */ Predicat...【详细内容】
2021-10-18  Tags: 函数  点击:(56)  评论:(0)  加入收藏
0 前言Hello,大家好,欢迎来到『自由技艺』的 C++ 系列专题。代码重用,尽可能避免冗余代码是程序员的一项必备技能,今天就来给大家介绍其中一种:函数装饰器。在设计模式中,与它对应...【详细内容】
2021-09-28  Tags: 函数  点击:(75)  评论:(0)  加入收藏
写一个简单的无参装饰器,计算math_info所需要的时间# 1, 基础函数,计算出2的16次方的值def math_info(): print("2**16的结果是:",2**64)# 2, 计算wrapper所需要的时间impor...【详细内容】
2021-08-31  Tags: 函数  点击:(76)  评论:(0)  加入收藏
1、checkdate()验证格利高里日期即:日期是否存在。checkdate(month,day,year);month必需。一个从 1 到 12 的数字,规定月。day必需。一个从 1 到 31 的数字,规定日。year必需。...【详细内容】
2021-08-31  Tags: 函数  点击:(80)  评论:(0)  加入收藏
这些年一直保持着一个习惯,就是整理知识做成笔记,虽然很多资料我们上网去找一搜就是一大把,然后觉得不错的资料就把它们收藏起来,比如一些不错的博客,想着用到的时候再去翻看。但...【详细内容】
2021-08-27  Tags: 函数  点击:(62)  评论:(0)  加入收藏
▌简易百科推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  中年农码工    Tags:C#   点击:(0)  评论:(0)  加入收藏
引言在学习C语言或者其他编程语言的时候,我们编写的一个程序代码,基本都是在屏幕上打印出 hello world ,开始步入编程世(深)界(坑)的。C 语言版本的 hello world 代码:#include <std...【详细内容】
2021-12-21  一起学嵌入式    Tags:C 语言   点击:(10)  评论:(0)  加入收藏
读取SQLite数据库,就是读取一个路径\\192.168.100.**\position\db.sqlite下的文件<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/...【详细内容】
2021-12-16  今朝我的奋斗    Tags:c#   点击:(21)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  梦回故里归来    Tags:shell脚本   点击:(16)  评论:(0)  加入收藏
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  linux上的码农    Tags:c/c++   点击:(17)  评论:(0)  加入收藏
1.字符串数组+初始化char s1[]="array"; //字符数组char s2[6]="array"; //数组长度=字符串长度+1,因为字符串末尾会自动添&lsquo;\0&lsquo;printf("%s,%c\n",s1,s2[2]);...【详细内容】
2021-12-08  灯-灯灯    Tags:C语言   点击:(46)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  小智雅汇    Tags:函数   点击:(19)  评论:(0)  加入收藏
一、问题提出问题:把m个苹果放入n个盘子中,允许有的盘子为空,共有多少种方法?注:5,1,1和1 5 1属同一种方法m,n均小于10二、算法分析设f(m,n) 为m个苹果,n个盘子的放法数目,则先对...【详细内容】
2021-11-17  C语言编程    Tags:C语言   点击:(46)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  深度Linux    Tags:C++   点击:(37)  评论:(0)  加入收藏
OpenCV(Open Source Computer Vision Library)是一个(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效---由一系列...【详细内容】
2021-11-11  zls315    Tags:C#   点击:(50)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条