C++ 在C++;,可变函数(参数列表末尾带有…的函数)是否必须遵循_cdecl调用约定?

C++ 在C++;,可变函数(参数列表末尾带有…的函数)是否必须遵循_cdecl调用约定?,c++,variadic-functions,stdcall,cdecl,C++,Variadic Functions,Stdcall,Cdecl,我知道u stdcall函数不能有省略号,但我想确定的是,除了u cdecl或u stdcall之外,没有任何平台支持stdarg.h函数调用约定。调用约定必须是调用方从堆栈中清除参数的约定(因为被调用方不知道将传递什么) 但这并不一定与微软所说的“cdecl”相符。举个例子,在SPARC上,它通常会在寄存器中传递参数,因为这就是SPARC的工作原理——它的寄存器基本上是一个调用堆栈,如果调用深度太深,无法再放入寄存器,就会溢出到主内存中 虽然我不太确定,但我希望IA64(安腾)上的情况大致相同

我知道u stdcall函数不能有省略号,但我想确定的是,除了u cdecl或u stdcall之外,没有任何平台支持stdarg.h函数调用约定。

调用约定必须是调用方从堆栈中清除参数的约定(因为被调用方不知道将传递什么)

但这并不一定与微软所说的“cdecl”相符。举个例子,在SPARC上,它通常会在寄存器中传递参数,因为这就是SPARC的工作原理——它的寄存器基本上是一个调用堆栈,如果调用深度太深,无法再放入寄存器,就会溢出到主内存中

虽然我不太确定,但我希望IA64(安腾)上的情况大致相同——它也有一个巨大的寄存器集(如果内存可用,则有几百个)。如果我没有弄错的话,关于如何使用寄存器,这是比较宽容的,但我希望它至少在很多时候都会被类似地使用

为什么这对你很重要?使用stdarg.h及其宏的目的是在代码中隐藏调用约定的差异,以便可以移植地处理变量参数


编辑,基于评论:好的,现在我明白你在做什么了(至少足以改进答案)。考虑到您已经(显然)有代码来处理默认ABI中的变化,事情就简单多了。这只留下了一个问题,变量函数是否总是使用“默认ABI”,不管手头的平台是什么。由于“stdcall”和“default”是唯一的选项,我认为答案是肯定的。例如,在Windows上,
wsprintf
wprintf
打破了经验法则,使用cdecl调用约定而不是stdcall。

你的意思是“MSVC支持的平台”还是作为一般规则?即使你局限于MSVC支持的平台,你仍然会遇到这样的情况,比如一“调用约定,调用约定称为
\uu stdcall
,但它肯定不同于x86上的
\uu stdcall

好的,调用约定的多样性是x86上DOS/Windows独有的。大多数其他平台的编译器都随操作系统一起提供,并将约定标准化。

确定这一点的最明确方法是分析调用约定。要使可变函数发挥作用,调用约定需要几个属性:

  • 被调用方必须能够从堆栈顶部的固定偏移量访问不属于变量参数列表的参数。这需要编译器从右向左将参数推送到堆栈上。(这包括
    printf
    的第一个参数、格式规范。此外,变量参数列表本身的地址也必须从已知位置派生。)
  • 函数返回后,调用者必须负责从堆栈中删除参数,因为只有编译器在为调用者生成代码时才知道有多少参数首先被推到堆栈上。变量函数本身没有此信息
stdcall
无法工作,因为被调用方负责从堆栈中弹出参数。在旧的16位Windows时代,
pascal
无法工作,因为它将参数从左到右推送到堆栈上


当然,正如其他答案所提到的,许多平台在调用约定方面没有给您任何选择,这使得这个问题与那些平台无关。

考虑一下x86系统上的以下功能:

void(字符*,…)

函数将自己声明为u stdcall,这是一种被调用方清理约定。但是变量函数不能被调用者清除,因为被调用者不知道传递了多少参数,所以它不知道应该清除多少参数

Microsoft Visual Studio C/C++编译器通过静默地将调用约定转换为_cdecl来解决此冲突,这是不接受隐藏此参数的函数唯一支持的变量调用约定

为什么此转换以静默方式进行,而不是生成警告或错误

我猜这是为了让编译器选项/Gr(将默认调用约定设置为u fastcall)和/Gz(将默认调用约定设置为u stdcall)不那么烦人

将可变函数自动转换为_cdecl意味着您只需将/Gr或/Gz命令行开关添加到编译器选项中,所有内容仍将编译并运行(仅使用新的调用约定)

看待这一点的另一种方式不是将编译器看作是将可变函数stdcall转换为cdecl,而是简单地说“对于可变函数,stdcall是调用方干净的。”


您对SPARC的评论适用于许多(如果不是所有的话)RISC体系结构。由于寄存器非常丰富,编译器只使用这些寄存器来传递参数。更不用说,它们比内存快很多,特别是因为您可能要传递寄存器中已经存在的数据(尽管我想如果以后需要,它需要将它们推到堆栈上以恢复它们,但是…)。无论如何,我知道这就是它在MIPS上的工作方式,我很确定PPC也是这样工作的。“为什么这对你很重要?”Ben正在为Mozilla开发js ctypes。你可以在(搜索“疯狂的想法”)中看到确切的上下文,你可以看到一些解释js ctypes的文档,不用说,js ctypes仅适用于应用程序代码和加载项,而不是网页。目前我们有一种定义C函数的方法