C/C++ 中有哪些不同的调用约定? 分别是什么意思?
C/C++ 中有不同的调用约定:stdcall
、extern
、pascal
等。有多少种这样的调用约定,以及有哪些各是什么意思? 有描述这些的链接吗?
There are different calling conventions available in C/C++: stdcall
, extern
, pascal
, etc. How many such calling conventions are available, and what do each mean? Are there any links that describe these?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
简单回答:我使用 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 中传递。
pascal - 参数按从左到右的顺序压入堆栈(与 cdecl 相反),被调用者负责在返回之前平衡堆栈。
stdcall - stdcall[4] 调用约定是 Pascal 调用约定的变体,其中被调用者负责清理堆栈,但参数从右到左压入堆栈顺序,如 _cdecl 调用约定中所示。 寄存器 EAX、ECX 和 EDX 指定在函数内使用。 返回值存储在 EAX 寄存器中。
fastcall - __fastcall 约定(又名 __msfastcall)传递适合 ECX 和 EDX 的前两个参数(从左到右计算)。 剩余的参数从右到左压入堆栈。
vectorcall - 在 Visual Studio 2013 中,Microsoft 引入了 __vectorcall 调用约定,以响应游戏、图形、视频/音频和编解码器开发人员的效率问题。[7] 对于 IA-32 和 x64 代码, __vectorcall 分别类似于 __fastcall 和原始 x64 调用约定,但将它们扩展为支持使用 SIMD 寄存器传递向量参数。 对于 x64,当前 6 个参数中的任何一个为向量类型(float、double、__m128、__m256 等)时,它们将通过相应的 XMM/YMM 寄存器传入。 与 IA-32 类似,无论位置如何,从左到右为向量类型参数顺序分配最多 6 个 XMM/YMM 寄存器。 此外,__vectorcall 添加了对传递同构向量聚合 (HVA) 值的支持,这些值是使用相同的六个寄存器仅由最多四个相同向量类型组成的复合类型。 一旦为向量类型参数分配了寄存器,未使用的寄存器就会从左到右分配给 HVA 参数,无论位置如何。 使用前四个 XMM/YMM 寄存器返回结果向量类型和 HVA 值。
safecall - 在 Microsoft Windows 上的 Delphi 和 Free Pascal 中,safecall 调用约定封装了 COM(组件对象模型)错误处理,因此异常不会泄漏给调用者,而是在 HRESULT 返回中报告值,按照 COM/OLE 的要求。 当从 Delphi 代码调用 safecall 函数时,Delphi 还会自动检查返回的 HRESULT 并在必要时引发异常。
safecall 调用约定与 stdcall 调用约定相同,不同之处在于异常在 EAX 中作为 HResult(而不是在 FS:[0] 中)传递回调用者,而函数结果在堆栈上通过引用传递为尽管它是最终的“输出”参数。 当从 Delphi 调用 Delphi 函数时,此调用约定将像任何其他调用约定一样出现,因为尽管异常在 EAX 中传回,但调用者会自动将它们转换回正确的异常。 当使用以其他语言创建的 COM 对象时,HResults 将自动引发异常,并且 Get 函数的结果位于结果中而不是参数中。 当在 Delphi 中使用 safecall 创建 COM 对象时,无需担心 HResults,因为异常可以正常引发,但在其他语言中将被视为 HResults。
Microsoft X64 调用约定 - Windows 和预启动 UEFI 遵循 Microsoft x64 调用约定[12][13](适用于 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 被视为易失性(调用者保存)。[15]
寄存器 RBX、RBP、RDI、RSI、RSP、R12、R13、R14 和 R15 被视为非易失性(被调用者保存)。[15]
例如,采用 5 个整数参数的函数将采用寄存器中的第一个到第四个参数,第五个参数将被推送到影子空间的顶部。 因此,当进入被调用函数时,堆栈将由(按升序)返回地址、后面的影子空间(32 字节)和第五个参数组成。
在 x86-64 中,Visual Studio 2008 将浮点数存储在 XMM6 和 XMM7(以及 XMM8 到 XMM15)中; 因此,对于 x86-64,用户编写的汇编语言例程必须保留 XMM6 和 XMM7(与 x86 相比,其中用户编写的汇编语言例程不需要保留 XMM6 和 XMM7)。 换句话说,当从 x86 移植到 x86-64 时,用户编写的汇编语言例程必须在函数之前/之后更新以保存/恢复 XMM6 和 XMM7。
Simple answer: I use cdecl, stdcall, and fastcall. I seldom use fastcall. stdcall is used to call Windows API functions.
Detailed answer (Stolen from Wikipedia):
cdecl - In cdecl, subroutine arguments are passed on the stack. Integer values and memory addresses are returned in the EAX register, floating point values in the ST0 x87 register. Registers EAX, ECX, and EDX are caller-saved, and the rest are callee-saved. The x87 floating point registers ST0 to ST7 must be empty (popped or freed) when calling a new function, and ST1 to ST7 must be empty on exiting a function. ST0 must also be empty when not used for returning a value.
syscall - This is similar to cdecl in that arguments are pushed right-to-left. EAX, ECX, and EDX are not preserved. The size of the parameter list in doublewords is passed in AL.
pascal - the parameters are pushed on the stack in left-to-right order (opposite of cdecl), and the callee is responsible for balancing the stack before return.
stdcall - The stdcall[4] calling convention is a variation on the Pascal calling convention in which the callee is responsible for cleaning up the stack, but the parameters are pushed onto the stack in right-to-left order, as in the _cdecl calling convention. Registers EAX, ECX, and EDX are designated for use within the function. Return values are stored in the EAX register.
fastcall - __fastcall convention (aka __msfastcall) passes the first two arguments (evaluated left to right) that fit into ECX and EDX. Remaining arguments are pushed onto the stack from right to left.
vectorcall - In Visual Studio 2013, Microsoft introduced the __vectorcall calling convention in response to efficiency concerns from game, graphic, video/audio, and codec developers.[7] For IA-32 and x64 code, __vectorcall is similar to __fastcall and the original x64 calling conventions respectively, but extends them to support passing vector arguments using SIMD registers. For x64, when any of the first six arguments are vector types (float, double, __m128, __m256, etc.), they are passed in via the corresponding XMM/YMM registers. Similarly for IA-32, up to six XMM/YMM registers are allocated sequentially for vector type arguments from left to right regardless of position. Additionally, __vectorcall adds support for passing homogeneous vector aggregate (HVA) values, which are composite types consisting solely of up to four identical vector types, using the same six registers. Once the registers have been allocated for vector type arguments, the unused registers are allocated to HVA arguments from left to right regardless of position. Resulting vector type and HVA values are returned using the first four XMM/YMM registers.
safecall - n Delphi and Free Pascal on Microsoft Windows, the safecall calling convention encapsulates COM (Component Object Model) error handling, thus exceptions aren't leaked out to the caller, but are reported in the HRESULT return value, as required by COM/OLE. When calling a safecall function from Delphi code, Delphi also automatically checks the returned HRESULT and raises an exception if necessary.
The safecall calling convention is the same as the stdcall calling convention, except that exceptions are passed back to the caller in EAX as a HResult (instead of in FS:[0]), while the function result is passed by reference on the stack as though it were a final "out" parameter. When calling a Delphi function from Delphi this calling convention will appear just like any other calling convention, because although exceptions are passed back in EAX, they are automatically converted back to proper exceptions by the caller. When using COM objects created in other languages, the HResults will be automatically raised as exceptions, and the result for Get functions is in the result rather than a parameter. When creating COM objects in Delphi with safecall, there is no need to worry about HResults, as exceptions can be raised as normal but will be seen as HResults in other languages.
Microsoft X64 Calling Convention - The Microsoft x64 calling convention[12][13] is followed on Windows and pre-boot UEFI (for long mode on x86-64). It uses registers RCX, RDX, R8, R9 for the first four integer or pointer arguments (in that order), and XMM0, XMM1, XMM2, XMM3 are used for floating point arguments. Additional arguments are pushed onto the stack (right to left). Integer return values (similar to x86) are returned in RAX if 64 bits or less. Floating point return values are returned in XMM0. Parameters less than 64 bits long are not zero extended; the high bits are not zeroed.
When compiling for the x64 architecture in a Windows context (whether using Microsoft or non-Microsoft tools), there is only one calling convention – the one described here, so that stdcall, thiscall, cdecl, fastcall, etc., are now all one and the same.
In the Microsoft x64 calling convention, it is the caller's responsibility to allocate 32 bytes of "shadow space" on the stack right before calling the function (regardless of the actual number of parameters used), and to pop the stack after the call. The shadow space is used to spill RCX, RDX, R8, and R9,[14] but must be made available to all functions, even those with fewer than four parameters.
The registers RAX, RCX, RDX, R8, R9, R10, R11 are considered volatile (caller-saved).[15]
The registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15 are considered nonvolatile (callee-saved).[15]
For example, a function taking 5 integer arguments will take the first to fourth in registers, and the fifth will be pushed on the top of the shadow space. So when the called function is entered, the stack will be composed of (in ascending order) the return address, followed by the shadow space (32 bytes) followed by the fifth parameter.
In x86-64, Visual Studio 2008 stores floating point numbers in XMM6 and XMM7 (as well as XMM8 through XMM15); consequently, for x86-64, user-written assembly language routines must preserve XMM6 and XMM7 (as compared to x86 wherein user-written assembly language routines did not need to preserve XMM6 and XMM7). In other words, user-written assembly language routines must be updated to save/restore XMM6 and XMM7 before/after the function when being ported from x86 to x86-64.
标准 C++ 基本上有两个:
extern "C"
和extern "C++"
。 后者是默认值; 当您需要链接到 C 代码时使用前者。 编译器可以定义除“C”和“C++”之外的其他字符串。 例如,与其 Pascal 兄弟兼容的编译器可能会定义extern "Pascal"。
不幸的是,一些编译器发明了关键字。 在这些情况下,请参阅编译器文档。
Standard C++ basically has two:
extern "C"
andextern "C++"
. The latter is the default; this former used when you need to link to C code. Compilers may define other strings besides "C" and "C++". For instance, a compiler that's compatible with its Pascal sibling may defineextern "Pascal".
Unfortunately, some compilers have invented keywords instead. In these cases, see the compiler documentation.
这些涉及到将参数放在调用堆栈上的顺序,以及何时使用按值调用和/或按引用调用语义。 它们是编译器特定的扩展,旨在简化多语言编程。
These concern what order to put parameters on the call stack, and when to use call by value and/or call by reference semantics. They are compiler specific extensions intended to simplify multilingual programming.
标准 C 和标准 C++ 都没有这样的概念 - 这些是特定编译器、链接器和/或操作系统的功能,因此您应该真正指出您对哪些特定技术感兴趣。
Neither Standard C nor Standard C++ has such a concept - these are features of specific compilers, linkers and/or operating systems, so you should really indicate which specific technologies you are interested in.
fastcall 是最优化的,但没有人使用它
fastcall is the optimized one but nobody uses it
它们是调用某些库中的函数(特别是 Win32 API)所需的特定于平台的扩展。 尽管 MSVC 的选项是 x86 上 Windows 的事实上标准,但它们是非标准的并且特定于每个编译器。 通常,需要它们的库会在头文件中声明它们,并且它们将透明地工作。 它们之间的主要区别在于,C 历来使用效率较低的约定,允许任意类型的可变数量的参数,而 Windows 和大多数其他语言的做法有所不同。 不过,许多差异是相当随意的,例如左手或右手推动以及让调用者或被调用函数进行清理。
它们很大程度上与 64 位代码无关:关于调用约定的圣战从未在这些平台上发生过。
在一些常见情况下,您可能需要将其中之一添加到函数中。 需要与用其他语言(有时甚至是其他 C++ 编译器)编写的模块链接的 C++ 模块必须使用
extern "C"
命名约定以实现兼容性。 回调函数需要使用与调用者相同的调用约定,对于 Windows API 来说是CALLBACK
,而不是默认的。 共享库可能需要使用与内部使用不同的调用约定导出其函数,或者可能希望显式使用 __cdecl 以防默认更改。 在某些平台上,您可能会或可能不会从 __fastcall 获得更好的性能:它主要通过一两个参数来加速短叶函数,并且可能会使某些程序变慢。They’re platform-specific extensions needed to call functions in certain libraries, particularly the Win32 API. They’re nonstandard and specific to each compiler, although MSVC’s options are the de facto standard for Windows on x86. Normally, a library that needs them will declare them in the header files and they will work transparently. The main difference between them is that C historically used a less-efficient convention that allowed for a variable number of arguments of any type, while Windows and most other languages did it differently. A lot of the differences, though, such as pushing left-handed or right-handed and having the caller or the called function clean up, were pretty arbitrary.
They’re largely irrelevant to 64-bit code: the holy wars over calling conventions never happened on those platforms.
There are a few common cases where you might need to add one of these to a function. A C++ module that needs to link with modules written in other languages (and sometimes even other C++ compilers) will have to use the
extern "C"
naming convention for compatibility. A callback function needs to use the same calling convention as the caller, which with the Windows API isCALLBACK
, not the default. A shared library might need to export its functions with a different calling convention than it uses internally, or might want to make its use of__cdecl
explicit in case the default changes. You might or might not get better performance from__fastcall
on some platforms: it mostly speeds up short leaf functions with one or two parameters, and could make some programs slower.