C / C ++中有不同的调用约定:stdcall
,extern
,pascal
等。有多少这样的调用约定可用,各自的含义是什么?是否有描述这些的链接?
答案 0 :(得分:19)
标准C和标准C ++都没有这样的概念 - 这些是特定编译器,链接器和/或操作系统的特性,因此您应该真正指出您感兴趣的特定技术。
答案 1 :(得分:8)
简单回答:我使用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,当前六个参数中的任何一个是矢量类型(float,double,__ m128,__ m256等)时,它们通过相应的XMM / YMM寄存器传入。类似地,对于IA-32,无论位置如何,从左到右依次为向量类型参数分配多达六个XMM / YMM寄存器。此外,__ vectorcall增加了对传递齐次向量聚合(HVA)值的支持,这些值是由最多四个相同向量类型组成的复合类型,使用相同的六个寄存器。一旦为矢量类型参数分配了寄存器,就会将未使用的寄存器从左到右分配给HVA参数,而不管位置如何。使用前四个XMM / YMM寄存器返回生成的矢量类型和HVA值。
safecall - 在Microsoft Windows上的Delphi和Free Pascal,safecall调用约定封装了COM(组件对象模型)错误处理,因此异常不会泄露给调用者,但是按照COM / OLE的要求在HRESULT返回值中报告。当从Delphi代码调用safecall函数时,Delphi还会自动检查返回的HRESULT并在必要时引发异常。
safecall调用约定与stdcall调用约定相同,除了异常作为HResult(而不是FS:[0])传递回EAX中的调用者,而函数结果通过引用传递堆栈好像是最终的" out"参数。当从Delphi调用Delphi函数时,这个调用约定将像任何其他调用约定一样出现,因为虽然异常在EAX中传回,但它们会被调用者自动转换回适当的异常。使用以其他语言创建的COM对象时,HResults将自动引发为异常,Get函数的结果将在结果中而不是参数中。使用safecall在Delphi中创建COM对象时,无需担心HResults,因为异常可以正常引发,但会被视为其他语言的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]但必须可用于所有函数,即使参数少于四个的函数。
寄存器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。
答案 2 :(得分:5)
标准C ++基本上有两个:extern "C"
和extern "C++"
。后者是默认的;当您需要链接到C代码时使用此前者。编译器可以定义除“C”和“C ++”之外的其他字符串。例如,与其Pascal兄弟兼容的编译器可以定义extern "Pascal".
不幸的是,一些编译器发明了关键字。在这些情况下,请参阅编译器文档。
答案 3 :(得分:2)
这些涉及将参数放在调用堆栈上的顺序,以及何时使用值调用和/或按引用语义调用。它们是特定于编译器的扩展,旨在简化多语言编程。
答案 4 :(得分:0)
它们是调用某些库中的函数所需的特定于平台的扩展,尤其是Win32 API。它们是非标准的并且特定于每个编译器,尽管MSVC的选项是x86上Windows的事实上的标准。通常,需要它们的库会在头文件中声明它们,它们将以透明方式工作。它们之间的主要区别在于,C历史上使用了一种效率较低的约定,它允许任意类型的可变数量的参数,而Windows和大多数其他语言则采用不同的方式。但是,很多不同之处,例如左撇子或右撇子以及调用者或被叫函数的清理,都是非常随意的。
它们与64位代码基本无关:在这些平台上,调用约定的神圣战争从未发生过。
有些常见情况可能需要将其中一个添加到函数中。需要与其他语言(有时甚至是其他C ++编译器)编写的模块链接的C ++模块必须使用extern "C"
命名约定来实现兼容性。回调函数需要使用与调用者相同的调用约定,其中Windows API为CALLBACK
,而不是默认值。共享库可能需要使用与内部使用不同的调用约定来导出其函数,或者可能希望在默认更改时使用__cdecl
显式。在某些平台上,你可能会或者可能不会从__fastcall
获得更好的性能:它主要通过一个或两个参数加速短叶函数,并且可能使某些程序变慢。
答案 5 :(得分:-2)
fastcall是优化的,但没有人使用它