调用约定 | 参数压栈 | 平衡堆栈 |
---|---|---|
_cdecl | 从右至左 | 调用者清理 |
_stdcall | 从右至左 | 自身清理 |
_fastcall | ecx/edx 传送两个剩下的从右至左 | 自身清理 |
_thiscall | 参数从右向左入栈 | 由 ecx 传递 this 指针,自身清理 |
_x64call | 参数从右向左, RCX, RDX, R8, R9 | 自身清理 |
cdecl
cdecl(C declaration, 即 C 声明) 是源起 C 语言的一种调用约定, 也是 C 语言的事实上的标准.
函数实参在线程栈上按照从右至左的顺序依次压栈.
函数结果保存在寄存器 EAX/AX/AL 中
浮点型结果存放在寄存器 ST0 中
编译后的函数名前缀以一个下划线字符
调用者负责从线程栈中弹出实参 (即清栈)
8 比特或者 16 比特长的整形实参提升为 32 比特长.
受到函数调用影响的寄存器 (volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
RET 指令从函数被调用者返回到调用者 (实质上是读取寄存器 EBP 所指的线程栈之处保存的函数返回地址并加载到 IP 寄存器)
thiscall
在调用 C++ 非静态成员函数时使用此约定. 基于所使用的编译器和函数是否使用可变参数, 有两个主流版本的 thiscall. 对于 GCC 编译器, thiscall 几乎与 cdecl 等同: 调用者清理堆栈, 参数从右到左传递. 差别在于 this 指针, thiscall 会在最后把 this 指针推入栈中, 即相当于在函数原型中是隐式的左数第一个参数.
在微软 https://zh.wikipedia.org/wiki/微软 Visual C++ 编译器中, this 指针通过 ECX 寄存器传递, 其余同 cdecl 约定. 当函数使用可变参数, 此时调用者负责清理堆栈 (参考 cdecl).thiscall 约定只在微软 Visual C++ 2005 及其之后的版本被显式指定. 其他编译器中, thiscall 并不是一个关键字 (反汇编器如 IDA 使用__thiscall).
微软 x86-64 调用约定
在 Windows x64 环境下编译代码时, 只有一种调用约定, 也就是说 32 位下的各种约定在 64 位下统一成一种了.
微软 x64 调用约定使用 RCX, RDX, R8, R9 四个寄存器用于存储函数调用时的 4 个参数 (从左到右), 使用 XMM0, XMM1, XMM2, XMM3 来传递浮点变量. 其他的参数直接入栈 (从右至左). 整型返回值放置在 RAX 中, 浮点返回值在 XMM0 中. 少于 64 位的参数并没有做零扩展, 此时高位充斥着垃圾.
在微软 x64 调用约定中, 调用者的一个职责是在调用函数之前 (无论实际的传参使用多大空间), 在栈上的函数返回地址之上 (靠近栈顶) 分配一个 32 字节的 "影子空间"; 并且在调用结束后从栈上弹掉此空间. 影子空间是用来给 RCX, RDX, R8 和 R9 提供保存值的空间, 即使是对于少于四个参数的函数也要分配这 32 个字节.
例如, 一个函数拥有 5 个整型参数, 第一个到第四个放在寄存器中, 第五个就被推到影子空间之外的栈顶. 当函数被调用, 此栈用来组成返回值 ---- 影子空间 32 位 + 第五个参数.
在 x86-64 体系下, Visual Studio 2008 在 XMM6 和 XMM7 中 (同样的有 XMM8 到 XMM15) 存储浮点数. 结果对于用户写的汇编语言例程, 必须保存 XMM6 和 XMM7(x86 不用保存这两个寄存器), 这也就是说, 在 x86 和 x86-64 之间移植汇编例程时, 需要注意在函数调用之前 / 之后, 要保存 / 恢复 XMM6 和 XMM7.
来源: http://www.bubuko.com/infodetail-3341940.html