C调用约定__cdecl、__stdcall、__fastcall、__pascal分析

 

参考原文地址:https://www.cnblogs.com/yenyuloong/p/9626658.htmlhtml

 

C/C++ 中不一样的函数调用规则会生成不一样的机器代码,产生不一样的微观效果,接下来让咱们一块儿来浅析四种调用规则的原理和它们各自的异同。经过一段 C 语言代码来引导咱们的浅析过程。这里咱们编写了三个函数,它们的功能都是返回两个参数的相加结果,只是每一个函数都有不同的调用规则。编程

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

 咱们先介绍 __cdecl 与 __stdcall 调用规则,后面咱们会接着浅析 __fastcall 调用规则。函数

 首先,咱们得明白一个教条(其实也是本身归纳的),那就是 —— 调用规则的区别产生其实就是因为调用者与被调用者之间的“责任分配”问题。spa

 代码段中的第 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 拉高以维持堆栈平衡)。htm

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

image

   不难发现,__stdcall 调用规则使得被调用者来执行清栈操做(由被调用者函数自身将 ESP 拉高以维持堆栈平衡)这也是 __stdcall 与 __cdecl 调用规则的最根本的区别。内存

   __cdecl 偏向于把责任分配给调用者,动脑筋想一想,咱们的程序在 CALL __cdecl 调用规则的函数以前,把参数从右到左依次压栈,CALL 返回后,剩下的清栈操做都交给调用者处理,调用者负责拉高 ESP。再回来想一想 __stdcall,在 CALL 中将调用者的 EBP 压栈以保存现场,而后使 EBP 对齐于 ESP,而后经过 EBP + 偏移地址取得参数,而且通过加法获得 EAX 返回值,从堆栈弹出 EBP 恢复现场,可是最后不同的地方,程序将执行 “RETN 0x8” 将 ESP 拉回以前的 ESP + 8 的位置,换言之,被调用者将负责清栈操做。这就是以前所谓的“责任分配”的区别。ci

__fastcall 调用规则get

    不难揣测 fastcall 的英文意思貌似是“快速调用”,这一点与它的调用规则息息相关,它的快速是有缘由的,让咱们继续来看看以前那张反汇编的截图,代码段中的第 4 个就是 __fastcall 调用规则的 CALL。进 CALL 前,出乎意料地,程序将两个参数从右到左分别传给了 EDX,ECX 寄存器,讲到这里,学过计算机系统相关知识的人很容易理解为何这叫“快速调用”了,寄存器比内存快不少不少倍,能够认为传参给寄存器,要比在内存中更快得多,效率更高。

image

因为参数是直接传递给了寄存器,堆栈并未发生改变,在 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

从左到右

被调用者

相关文章
相关标签/搜索