c++ - C/C++ 中有哪些不同的调用约定,每个约定是什么意思?

标签 c++ c winapi visual-c++ calling-convention

C/C++ 中有不同的调用约定:stdcall , extern , pascal等。有多少这样的调用约定可用,每个约定是什么意思?有没有描述这些的链接?

最佳答案

简单回答:我使用 cdecl、stdcall 和 fastcall。我很少使用fastcall。 stdcall 用于调用 Windows API 函数。

详细答案(从 Wikipedia 盗取):

cdecl - 在 cdecl 中,子程序参数在堆栈上传递。整数值和内存地址在 EAX 寄存器中返回,浮点值在 ST0 x87 寄存器中返回。寄存器 EAX、ECX 和 EDX 是调用者保存的,其余的都是被调用者保存的。调用新函数时,x87 浮点寄存器 ST0 到 ST7 必须为空(弹出或释放),退出函数时 ST1 到 ST7 必须为空。当不用于返回值时,ST0 也必须为空。

系统调用 - 这类似于 cdecl,因为参数从右到左推送。 EAX、ECX 和 EDX 不会被保留。双字中参数列表的大小在 AL 中传递。

帕斯卡 - 参数按从左到右的顺序压入堆栈(与cdecl相反),被调用者负责在返回前平衡堆栈。

stdcall - stdcall[4] 调用约定是 Pascal 调用约定的变体,其中被调用者负责清理堆栈,但参数按从右到左的顺序压入堆栈,如 _cdecl 调用约定.寄存器 EAX、ECX 和 EDX 被指定在函数内使用。返回值存储在 EAX 寄存器中。

快速调用 - __fastcall 约定(又名 __msfastcall)传递适合 ECX 和 EDX 的前两个参数(从左到右计算)。剩余的参数从右到左压入堆栈。

vector 调用 - 在 Visual Studio 2013 中,Microsoft 引入了 __vectorcall 调用约定,以应对游戏、图形、视频/音频和编解码器开发人员的效率问题。[7]对于 IA-32 和 x64 代码,__vectorcall 分别类似于 __fastcall 和原始 x64 调用约定,但对其进行了扩展以支持使用 SIMD 寄存器传递 vector 参数。对于 x64,当前六个参数中的任何一个是 vector 类型(float、double、__m128、__m256 等)时,它们通过相应的 XMM/YMM 寄存器传入。类似地,对于 IA-32,无论位置如何,从左到右依次为 vector 类型参数分配最多六个 XMM/YMM 寄存器。此外,__vectorcall 增加了对传递同构 vector 聚合 (HVA) 值的支持,这些值是复合类型,仅由最多四个相同的 vector 类型组成,使用相同的六个寄存器。一旦为 vector 类型参数分配了寄存器,无论位置如何,未使用的寄存器都会从左到右分配给 HVA 参数。使用前四个 XMM/YMM 寄存器返回结果 vector 类型和 HVA 值。

安全电话 - n Microsoft Windows 上的 Delphi 和 Free Pascal,safecall 调用约定封装了 COM(组件对象模型)错误处理,因此异常不会泄漏给调用者,而是按照 COM/OLE 的要求在 HRESULT 返回值中报告.当从 Delphi 代码调用安全调用函数时,Delphi 还会自动检查返回的 HRESULT 并在必要时引发异常。

安全调用调用约定与 stdcall 调用约定相同,除了异常在 EAX 中作为 HResult(而不是在 FS:[0] 中)传递回调用者,而函数结果在堆栈上通过引用传递为尽管它是最终的“输出”参数。当从 Delphi 调用一个 Delphi 函数时,这个调用约定将像任何其他调用约定一样出现,因为尽管异常在 EAX 中传回,但它们会被调用者自动转换回正确的异常。当使用其他语言创建的 COM 对象时,HResults 将作为异常自动引发,并且 Get 函数的结果在结果中而不是参数中。在 Delphi 中使用 safecall 创建 COM 对象时,无需担心 HResults,因为异常可以正常引发,但在其他语言中将被视为 HResults。

Microsoft X64 调用约定 - Microsoft x64 调用约定[12][13] 在 Windows 和预启动 UEFI 上遵循(适用于 x86-64 上的长模式)。它使用寄存器 RCX、RDX、R8、R9 作为前四个整数或指针参数(按此顺序),而 XMM0、XMM1、XMM2、XMM3 用于浮点参数。额外的参数被压入堆栈(从右到左)。如果 64 位或更少,则在 RAX 中返回整数返回值(类似于 x86)。浮点返回值在 XMM0 中返回。长度小于 64 位的参数不进行零扩展;高位不归零。

在 Windows 上下文中为 x64 架构编译时(无论是使用 Microsoft 还是非 Microsoft 工具),只有一种调用约定——此处描述的一种,因此 stdcall、thiscall、cdecl、fastcall 等现在都是一个和相同的。

在 Microsoft x64 调用约定中,调用者有责任在调用函数之前在堆栈上分配 32 字节的“影子空间”(无论使用的实际参数数量如何),并在调用后弹出堆栈。阴影空间用于溢出 RCX、RDX、R8 和 R9,[14] 但必须可供所有函数使用,即使是那些参数少于四个的函数。

寄存器 RAX、RCX、RDX、R8、R9、R10、R11 被认为是 volatile 的(调用者保存的)。[15]

寄存器 RBX、RBP、RDI、RSI、RSP、R12、R13、R14 和 R15 被认为是非 volatile 的(被调用者保存的)。[15]

例如,一个带有 5 个整数参数的函数将采用寄存器中的第一个到第四个参数,第五个将被推到阴影空间的顶部。所以当被调用的函数进入时,堆栈将由(按升序)返回地址,后跟影子空间(32字节)后跟第五个参数组成。

在 x86-64 中,Visual Studio 2008 在 XMM6 和 XMM7(以及 XMM8 到 XMM15)中存储浮点数;因此,对于 x86-64,用户编写的汇编语言例程必须保留 XMM6 和 XMM7(与 x86 相比,其中用户编写的汇编语言例程不需要保留 XMM6 和 XMM7)。换句话说,当从 x86 移植到 x86-64 时,必须更新用户编写的汇编语言例程以在函数之前/之后保存/恢复 XMM6 和 XMM7。

关于c++ - C/C++ 中有哪些不同的调用约定,每个约定是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/949862/

相关文章:

c - Visual Studio 转换问题

c - 多线程和多进程时 fprintf 的行为如何?

visual-studio-2008 - 使用 VC++ 2008 为 XP 构建屏幕保护程序

windows - 如何处理 Windows CE 中的控制台关闭?

c++ - Visual C++ 无法编译 vector insert()

C++运行时问题

c 函数如何接收可变长度参数?

c - 模拟和 Win32 API 调用

c++ - 了解 PNG 文件格式 IDAT 段

c++ - 在 C++ 中检查 double 是否为 2 的幂而无需位操作的代码