C / C ++中有哪些不同的调用约定,每个含义是什么?

C / C ++中有不同的调用约定: stdcallexternpascal等。有多少这样的调用约定可用,各自的含义是什么? 有没有描述这些的链接?

标准C和标准C ++都没有这样的概念 – 这些是特定编译器,链接器和/或操作系统的特性,因此您应该真正指出您感兴趣的特定技术。

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

详细答案(从维基百科上偷来):

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

syscall – 这类似于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调用约定,以响应游戏,图形,video/音频和编解码器开发人员的效率问题。[7] 对于IA-32和x64代码,__ vectorcall分别类似于__fastcall和原始x64调用约定,但扩展它们以支持使用SIMD寄存器传递向量参数。 对于x64,当前六个参数中的任何一个是向量类型(float,double,__ m128,__ m256等)时,它们将通过相应的XMM / YMM寄存器传入。 类似地,对于IA-32,无论位置如何,从左到右依次为向量类型参数分配多达六个XMM / YMM寄存器。 此外,__ vectorcall添加了对传递齐次向量聚合(HVA)值的支持,这些值是仅由最多四个相同向量类型组成的复合类型,使用相同的六个寄存器。 一旦为矢量类型参数分配了寄存器,就会将未使用的寄存器从左到右分配给HVA参数,而不管位置如何。 使用前四个XMM / YMM寄存器返回生成的矢量类型和HVA值。

safecall – 在Microsoft Windows上的Delphi和Free Pascal,safecall调用约定封装了COM(组件对象模型)error handling,因此exception不会泄露给调用者,而是在COMESULT返回值中报告,如COM / OLE。 当从Delphi代码调用safecall函数时,Delphi还会自动检查返回的HRESULT,并在必要时引发exception。

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

Microsoft X64调用约定 – Windows上遵循Microsoft x64调用约定[12] [13]并预启动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]但必须可用于所有function,即使参数少于四个的function。

寄存器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。

标准C ++基本上有两个: extern "C"extern "C++" 。 后者是默认值; 当您需要链接到C代码时使用此前者。 编译器可以定义除“C”和“C ++”之外的其他字符串。 例如,与其Pascal兄弟兼容的编译器可以定义extern "Pascal".

不幸的是,一些编译器发明了关键字。 在这些情况下,请参阅编译器文档。

这些涉及将参数放在调用堆栈上的顺序,以及何时使用值调用和/或通过引用语义调用。 它们是特定于编译器的扩展,旨在简化多语言编程。

它们是调用某些库中函数所需的特定于平台的扩展,尤其是Win32 API。 它们是非标准的并且对每个编译器都是特定的,尽管MSVC的选项是x86上Windows的事实标准。 通常,需要它们的库会在头文件中声明它们,它们将以透明方式工作。 它们之间的主要区别在于,C历史上使用了一种效率较低的约定,允许任意类型的可变数量的参数,而Windows和大多数其他语言使用不同的方式。 但是,很多不同之处,例如左撇子或右撇子以及呼叫者或被叫function的清理,都是相当随意的。

它们在很大程度上与64位代码无关:在这些平台上,调用约定的神圣战争从未发生过。

在一些常见情况下,您可能需要将其中一个添加到函数中。 需要与其他语言(有时甚至是其他C ++编译器)编写的模块链接的C ++模块必须使用extern "C"命名约定来实现兼容性。 回调函数需要使用与调用者相同的调用约定,其中Windows API是CALLBACK ,而不是默认值。 共享库可能需要使用与内部使用不同的调用约定来导出其函数,或者可能希望在默认更改时使用__cdecl explicit。 在某些平台上,您可能会或者可能不会从__fastcall获得更好的性能:它主要通过一个或两个参数加速短叶函数,并且可能使某些程序变慢。

fastcall是优化的,但没有人使用它