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语言函数调用约定内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言函数调用约定和返回值详情

    目录 一.函数调用约定 1. 影响函数生成的符号名 2. 影响形参内存的释放者 _stdcall _fastcall _thiscall 二.函数的返回值 1. 0 < 返回值 <= 4字节 2. 4字节 < 返回值 <= 8字节 3. 返回值 > 8字节 一.函数调用约定 _cdecl:C调用约定 _stdcall:Windows标准的调用约定 _fastcall:快速调用约定 _thiscall:C++的成员函数调用约定 以上的函数调用约定入参都是从右向左,只有PASCA

  • C语言函数调用的三种实现方法实例

    目录 C语言函数 第一种方法 第二种方法 第三种方法 总结 C语言函数 1.概念:函数是一组一起执行一个任务的语句,每个c程序都必须有一个main函数,程序员可以把代码划分到不同的函数当中去,在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的.c标准库提供了可以大量调用的库函数,比如,printf,strcmp等. 2.函数的定义 c语言中函数的一般定义如下: return_type Function_name(Parameter List) { The function body }

  • C语言函数调用堆栈详情分析

    目录 一.C函数栈帧开辟以及回退过程 二.C函数调用约定和返回值 一.C函数栈帧开辟以及回退过程 __cdecl(C语言默认调用方式,函数参数8字节以内,使用push.本节采用此方式) main函数的栈帧调用sum函数的栈帧,sum函数栈帧使用完了以后回退都是怎么进行的,要搞清楚这个问题必须得看汇编代码,汇编代码分为两种:inter x86(windows)和AT&T(unix).这两种汇编非常相似,x86的汇编是从右向左看,unix的汇编是从左向右看的. 局部变量都是通过栈底指针ebp偏移访问

  • C语言之关于二维数组在函数中的调用问题

    目录 关于二维数组在函数中的调用问题 函数调用二维数组 二维数组如何放到函数中使用 下面以一个二维矩阵的转置为例 关于二维数组在函数中的调用问题 之前在学习二维数组的时候感觉理解起来很简单,所以理解一下就过去了,但是当自己真正的去用二维数组数组解决一些问题(特别是在函数调用二维数组的过程中)才真正发现原来使用起来还是要去注意一些细节的.废话不多说,直接上干货! 函数调用二维数组 在函数中调用二维数组的具体格式如下: 1.声明:这是声明的两种格式,在这里定义的是一个N*5的二维字符数组,各位看官一

  • C语言函数的递归调用详情

    目录 一.什么是递归 二.递归与迭代 一.什么是递归 程序调用自身的编程技巧称为递归( recursion) .递归做为一种算法在程序设计语言中广泛应用.一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量.递归的主要思考方式在于:把大事化小 递归的两个必要条件: 存在限制条件,当满足这个限制条件的时候,递归便不再继续. 每次

  • C语言三种函数调用约定_cdecl与_stdcall及_fastcall详细讲解

    目录 C语言常用的调用约定 一._cdecl调用约定 二._stdcall调用约定 三._fastcall调用约定 总结 C语言常用的调用约定 以下就是C语言常用的三种调用约定: 调用约定 参数压栈顺序 平衡堆栈 __cdecl 从右往左依次入栈 调用者清理堆栈 __stdcall 从右往左依次入栈 自身清理堆栈 __fastcall ECX/EDX传递前两个参数 剩下的从右往左依次入栈 自身清理堆栈 下面会举例为大家讲解三种调用约定的区别. 一._cdecl调用约定 这是C语言默认的调用约定,

  • Java语言----三种循环语句的区别介绍

    第一种:for循环 循环结构for语句的格式: for(初始化表达式;条件表达式;循环后的操作表达式) { 循环体;   } eg: class Dome_For2{ public static void main(String[] args) { //System.out.println("Hello World!"); //求1-10的偶数的和 int sum = 0; for (int i = 1;i<=10 ; i++ ) { if (i%2 ==0) { //判断语句

  • C语言三种方法解决轮转数组问题

    目录 题目 1.题目描述 2.要求 3.原题链接 二.相关知识点 三.解决思路 旋转法 直接法 空间换取时间 题目 1.题目描述 给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数. 示例 1: 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4] 2.要求 进阶

  • Go语言net包RPC远程调用三种方式http与json-rpc及tcp

    目录 一.服务端 二.http客户端 三.TCP客户端 四.json客户端 五.运行结果 rpc有多种调用方式,http.json-rpc.tcp 一.服务端 在代码中,启动了三个服务 package main import ( "log" "net" "net/http" "net/rpc" "net/rpc/jsonrpc" "sync" ) //go对RPC的支持,支持三个级别:T

  • python 三种方法提取pdf中的图片

    有时我们需要将一份或者多份PDF文件中的图片提取出来,如果采取在线的网站实现的话又担心图片泄漏,手动操作又觉得麻烦,其实用Python也可以轻松搞定! 今天就跟大家系统分享几种Python提取 PDF 图片的方法.其实没有非常完美的方法,每种方法提取效率都不是百分之百,因此可以考虑用多种方法进行互补,主要将涉及: 基于 fitz 库和正则搜索提取图片 基于 pdf2image 库的两种方法提取图片 基于 fitz 库和正则搜索 fitz 是 pymupdf 的子模块,需要先用命令行安装 pymu

  • SpringBoot定制三种错误页面及错误数据方法示例

    目录 定制错误页面 自定义 error.html 自定义动态错误页面 自定义静态错误页面 定制错误数据 1. 自定义异常处理类 2. 自定义错误属性处理工具 我们知道 Spring Boot 已经提供了一套默认的异常处理机制,但是 Spring Boot 提供的默认异常处理机制却并不一定适合我们实际的业务场景,因此,我们通常会根据自身的需要对 Spring Boot 全局异常进行统一定制,例如定制错误页面,定制错误数据等. 定制错误页面 我们可以通过以下 3 种方式定制 Spring Boot

  • C语言中斐波那契数列的三种实现方式(递归、循环、矩阵)

    目录 一.递归 二.循环 三.矩阵 <剑指offer>里讲到了一种斐波那契数列的 O(logN) 时间复杂度的实现,觉得挺有意思的,三种方法都记录一下. 一.递归 一般来说递归实现的代码都要比循环要简洁,但是效率不高,比如递归计算斐波那契数列第n个元素. long long Fibonacci_Solution1(unsigned int n) { // printf("%d ", n); if (n <= 0) return 0; if (n == 1) retur

  • Java语言通过三种方法实现队列的示例代码

    目录 队列 图解 数组模拟队列 队列优化—循环队列 代码 使用java内部队列 代码 队列 队列是一种特殊的线性表,只允许在表的前端进行删除操作,在表的后端进行插入操作. 队列是一个有序列表,可以用数组或是链表来实现. 遵循先入先出的原则.即:先存入队列的数据,要先取出.后存入的要后取出. 就相当于我们日常生活中的排队,先来先服务,后来的只能在后面进行排队等待. 图解 数组模拟队列 通过对定义的了解,发现队列很像我们的数组,那我们是不是可以通过数组来模拟队列,下面我们来实践一下. 首先先分析一下

随机推荐