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

What are the different calling conventions in C/C++ and what do each mean?

在C/C++中有不同的调用约定:EDOCX1,0,EDCOX1,1,EDCOX1,2,等等。有多少这样的调用约定是可用的,每一个都意味着什么?有描述这些的链接吗?


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


简单回答:我使用cdecl、stdcall和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调用约定的一种变体,在pascal调用约定中,被调用方负责清理堆栈,但参数按从右到左的顺序推送到堆栈上,如_cdecl调用约定中所示。寄存器eax、ecx和edx指定在函数中使用。返回值存储在EAX寄存器中。好的。

FastCall-uuFastCall约定(又名uUmsFastCall)传递符合ECX和EDX的前两个参数(从左到右计算)。其余参数从右向左推到堆栈上。好的。

vectorcall-在Visual Studio 2013中,微软针对游戏、图形、视频/音频和编解码器开发人员的效率问题引入了vectorcall调用约定。【7】对于IA-32和X64代码,vectorcall分别与fastcall和原来的X64调用约定相似,但扩展了它们以支持传递vector arg。使用SIMD寄存器的参数。对于X64,当前六个参数中的任何一个是向量类型(float、double、uuum128、uuum256等)时,它们通过相应的xmm/ymm寄存器传入。同样,对于IA-32,无论位置如何,从左到右依次为矢量类型参数分配最多6个xmm/ymm寄存器。此外,vectorcall增加了对传递同构向量聚合(hva)值的支持,同构向量聚合(hva)值是仅由四个相同的向量类型组成的复合类型,使用相同的六个寄存器。一旦为向量类型参数分配了寄存器,不管位置如何,都会将未使用的寄存器从左到右分配给hva参数。结果向量类型和hva值使用前四个xmm/ymm寄存器返回。好的。型

safecall-n delphi和free pascal在Microsoft Windows上,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和预引导UEFI(用于x86-64的长模式)上,遵循Microsoft X64调用约定[12][13]。它使用寄存器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(与用户编写的汇编语言例程不需要保留xmm6和xmm7的x86相比)。换句话说,当从x86移植到x86-64时,必须更新用户编写的汇编语言例程,以在函数之前/之后保存/还原xmm6和xmm7。好的。好啊。


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

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


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


它们是特定于平台的扩展,需要调用某些库中的函数,特别是win32 api。它们是非标准的,并且特定于每个编译器,尽管MSVC的选项实际上是x86上Windows的标准。通常,需要它们的库将在头文件中声明它们,并且它们将透明地工作。它们之间的主要区别在于,C历史上使用了一种效率较低的约定,这种约定允许任何类型的变量数目,而Windows和大多数其他语言则不同。但是,很多不同之处,比如左推或右推,让调用者或被调用函数清除,都是相当随意的。

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

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


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