C调用约定__cdecl、__stdcall、__fastcall、__pascal分析
参考原文地址:https://www.cnblogs.com/yenyuloong/p/9626658.html
C/C++ 中不同的函数调用规则会生成不同的机器代码,产生不同的微观效果,接下来让我们一起来浅析四种调用规则的原理和它们各自的异同。通过一段 C 语言代码来引导我们的浅析过程。这里我们编写了三个函数,它们的功能都是返回两个参数的相加结果,只是每个函数都有不一样的调用规则。

用 OllyDBG 查看了编译后代码的“真面目”。代码中有 4 个 CALL,第一个是 printf,我们不关心这个。后面三个分别是具有 __cdecl,__stdcall,__fastcall 调用规则的函数 CALL(这里我已经做了注释)。

我们先介绍 __cdecl 与 __stdcall 调用规则,后面我们会接着浅析 __fastcall 调用规则。
首先,我们得明白一个教条(其实也是自己概括的),那就是 —— 调用规则的区别产生其实就是由于调用者与被调用者之间的“责任分配”问题。
代码段中的第 2 个就是 __cdecl 调用规则的 CALL。__cdecl 是 C/C++、MFC 默认的调用规则。我们可以看到,在执行 CALL 之前,程序会将参数按照从右到左的方式压栈,这里是两个整型参数,每压栈一个 ESP 都会减 4,这样下来 ESP 会减少 8,然后 CALL 这个函数。常规地,我们可以看到,这个 CALL 里面参数的处理和通常情况下一致,先将 EBP 压栈保存现场,然后使 EBP 重合于 ESP,再通过 EBP + 偏移地址来取得两个参数值,赋值再累加到 EAX 中,EAX 将作为返回值给调用者使用,还原 EBP 现场,调用 RETN 返回到调用者。最后,使得 ESP 加 8。哎!这刚好和开头对称嘛!为了堆栈平衡,ESP 最终又被拉回到了 CALL 之前的位置。我们暂且可以小结一下,实际上在 __cdecl 调用规则中,需要调用者来负责清栈操作(由调用者将 ESP 拉高以维持堆栈平衡)。

代码段中的第 3 个是 __stdcall 调用规则的 CALL。__stdcall 调用规则在 Win32 API 函数中用的比较多。跟 __cdecl 一样,在执行 CALL 之前,程序会先将参数从右到左依次压栈,我们跟进 CALL 里面,可以看到以下的反汇编代码,我们很容易发现,除了最后一条指令,其他的指令与 __cdecl 调用规则是基本一样的。最后一条指令是“RETN 0x8”,这是什么意思呢?实际上呢,就相当于先执行“ADD ESP, 0x8”再执行“POP EIP” 。换言之,就是将 ESP 加 8,然后正常 RETN 返回到调用者。

不难发现,__stdcall 调用规则使得被调用者来执行清栈操作(由被调用者函数自身将 ESP 拉高以维持堆栈平衡),这也是 __stdcall 与 __cdecl 调用规则的最根本的区别。
__cdecl 偏向于把责任分配给调用者,动脑筋想想,我们的程序在 CALL __cdecl 调用规则的函数之前,把参数从右到左依次压栈,CALL 返回后,剩下的清栈操作都交给调用者处理,调用者负责拉高 ESP。再回来想想 __stdcall,在 CALL 中将调用者的 EBP 压栈以保存现场,然后使 EBP 对齐于 ESP,然后通过 EBP + 偏移地址取得参数,并且经过加法得到 EAX 返回值,从堆栈弹出 EBP 恢复现场,但是最后不一样的地方,程序将执行 “RETN 0x8” 将 ESP 拉回之前的 ESP + 8 的位置,换言之,被调用者将负责清栈操作。这就是之前所谓的“责任分配”的区别。
__fastcall 调用规则
不难揣测 fastcall 的英文意思貌似是“快速调用”,这一点与它的调用规则息息相关,它的快速是有原因的,让我们继续来看看之前那张反汇编的截图,代码段中的第 4 个就是 __fastcall 调用规则的 CALL。进 CALL 前,出乎意料地,程序将两个参数从右到左分别传给了 EDX,ECX 寄存器,讲到这里,学过计算机系统相关知识的人很容易理解为什么这叫“快速调用”了,寄存器比内存快很多很多倍,可以认为传参给寄存器,要比在内存中更快得多,效率更高。

由于参数是直接传递给了寄存器,堆栈并未发生改变,在 CALL 中,EBP 压栈,EBP 和 ESP 对齐之后,ESP 减 8,这个操作有点像对局部变量分配堆栈空间,然后程序将 EDX,ECX 分别赋值给 EBP – 8 与 EBP – 4 这两个地址,这个过程相当于用寄存器给局部变量赋值,接下来运算结果将保存在 EAX 中,ESP 归位,EBP 恢复现场,最后 RETN 返回调用者领空。
本例只传送了两个整数型参数。其实呢,对于 __fastcall 调用规则,左边开始的两个不大于4字节(int)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送。并且,__fastcall 调用规则使得被调用者负责清理栈的操作(由被调用者函数自身将 ESP 拉高以维持堆栈平衡),这一点和 __stdcall 一样。
__pascal 调用规则
__pascal 是用于 Pascal / Delphi 编程语言的调用规则,C/C++ 中也可以使用这种调用规则。简单地说,__pascal 调用规则与 __stdcall 不同的地方就是压栈顺序恰恰相反,前面讲到的三种调用规则的压栈顺序都是从右到左依次入栈,__pascal 则是从左到右依次入栈。并且,被调用者(函数自身)将自行完成清栈操作,这和 __stdcall,__fastcall 一样。由于比较简单,我就没有做出示例。
小结
做个表格来小结一下,很直观就能看出这四种调用规则的异同:
|
调用规则 |
入栈顺序 |
清栈责任 |
|
__cdecl |
从右到左 |
调用者 |
|
__stdcall |
从右到左 |
被调用者 |
|
__fastcall |
从右到左(先 EDX、ECX,再到堆栈) |
被调用者 |
|
__pascal |
从左到右 |
被调用者 |
C调用约定__cdecl、__stdcall、__fastcall、__pascal分析的更多相关文章
- 函数的调用规则(__cdecl,__stdcall,__fastcall,__pascal)
关于函数的调用规则(调用约定),大多数时候是不需要了解的,但是如果需要跨语言的编程,比如VC写的dll要delphi调用,则需要了解. microsoft的vc默认的是__cdecl方式,而windo ...
- __cdecl,__stdcall,__fastcall,__pascal,__thiscall 的区别
关于函数的调用规则(调用约定),大多数时候是不需要了解的,但是如果需要跨语言的编程,比如VC写的dll要delphi调用,则需要了解. microsoft的vc默认的是__cdecl方式,而windo ...
- 调用约定__cdecl __fastcall与__stdcall
__cdecl __fastcall与__stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数弹出栈,3)以 ...
- __cdecl __stdcall __fastcall之函数调用约定讲解
首先讲解一下栈帧的概念: 从逻辑上讲,栈帧就是一个函数执行的环境:函数参数.函数的局部变量.函数执行完后返回到哪里等等. 实现上有硬件方式和软件方式(有些体系不支持硬件栈) 首先应该明白,栈是从高地址 ...
- 调用约定__cdecl和__stdcall
首先,__cdecl,c declaration,C风格声明.或者 c default calling(笔者瞎编的).(那么问题来了,为什么PASCAL风格被称为std?) 调用约定的内容包括三点:参 ...
- 调用约定__stdcall / __cdecl
__cdecl与__stdcall这两种调用约定之间的主要差别在于由谁来执行对参数的清理工作. 如果是__cdecl,那么主调函数将负责执行清理工作,如果是__stdcall那被调函数将负责执行清理. ...
- DLL导出与调用约定
一般来说,从DLL导出函数有两种方法.一种是使用.def文件:另一种是使用__declspec(dllexport). 使用上面两种方法各有优缺点.使用.def文件就是需要额外维护,当导出函数更改名字 ...
- X86调用约定 calling convention
http://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A 这里描述了在x86芯片架构上的调用约定(calling con ...
- C/C++:函数的调用约定(Calling Convention)和名称修饰(Decorated Name)以及两者不匹配引起的问题
转自:http://blog.csdn.net/zskof/article/details/3475182 注:C++有着与C不同的名称修饰,主要是为了解决重载(overload):调用约定则影响函数 ...
随机推荐
- H5前端的行业知识
今天咱们不说代码!咱们说点行内的只是吧!! 一.前端基本技能: 会点设计,不要求精湛,处理图片,设计个小广告是要的: 精通HTML+CSS,并能快速处理各浏览器兼容问题: 熟练掌握Javascript ...
- 在Eclipse安装Genymotion插件的经验心得
个人心得分享,不当之处还请指正. Eclipse自带的Android模拟器已经无力吐槽了,新手刚上手时或许配置完环境已经精疲力尽了,或许还沉浸在开发成功的喜悦当中,对AVD模拟器的运行情况关注不大,渐 ...
- 私网IP访问Internet
公网.内网是两种Internet的接入方式. 内网接入方式:上网的计算机得到的IP地址是Internet上的保留地址,保留地址有3种形式: A类地址:10.0.0.0~10.255.255.255 B ...
- Android studio 配置使用maven
安装nexus(略) 启动nexus 打开web(admin;admin123) http://127.0.0.1:8081/nexus 创建的demo 1 2 3 对应的本地目录 配置maven / ...
- Entity Framework:“无法加载指定的元数据资源
System.Data.Entity.Core.MetadataException:“无法加载指定的元数据资源 CodeFirst方式使用EF,写入数据时报错.System.Data.Entity.C ...
- 浅谈SQL Server中的事务日志(四)----在完整恢复模式下日志的角色
简介 生产环境下的数据是如果可以写在资产负债表上的话,我想这个资产所占的数额一定不会小.而墨菲定律(事情如果有变坏的可能,无论这种可能性有多小,它总会发生)仿佛是给DBA量身定做的.在上篇文章介绍的简 ...
- SVNKit学习——使用High-Level API管理Working Copy示例(六)
本篇内容是基于SVNKit High-Level API实现的针对Working copy的操作,操作内容与SVN图形化界面.命令行类似. High-Level API类图: 核心思想: 所有操作由各 ...
- shell定时采集数据到HDFS
上线的网站每天都会产生日志数据.假如有这样的需求:要求在凌晨 24 点开始操作前一天产生的日志文件,准实时上传至 HDFS 集群上. 该如何实现?实现后能否实现周期性上传需求?如何定时? Linux ...
- POJ 1681 Painter's Problem 【高斯消元 二进制枚举】
任意门:http://poj.org/problem?id=1681 Painter's Problem Time Limit: 1000MS Memory Limit: 10000K Total ...
- 【Calculus 微积分の一些个人理解】
微积分 微积分(Calculus)是高等数学中研究函数的微分(Differentiation).积分(Integration)以及有关概念和应用的数学分支.它是数学的一个基础学科.内容主要包括极限.微 ...