在 C++ 中,可变参数函数(参数列表末尾带有 ... 的函数)是否必须遵循 __cdecl 调用约定?
我知道 __stdcall 函数不能有省略号,但我想确保没有平台支持 stdarg.h 函数来调用除 __cdecl 或 __stdcall 之外的约定。
I know that __stdcall functions can't have ellipses, but I want to be sure there are no platforms that support the stdarg.h functions for calling conventions other than __cdecl or __stdcall.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
调用约定必须是调用者从堆栈中清除参数的约定(因为被调用者不知道将传递什么)。
但这并不一定对应于微软所说的“__cdecl”。举个例子,在 SPARC 上,它通常会在寄存器中传递参数,因为这就是 SPARC 的工作原理——它的寄存器基本上充当一个调用堆栈,如果调用足够深入,该堆栈就会溢出到主内存中他们将不再适合登记。
虽然我对此不太确定,但我预计 IA64 (Itanium) 上的情况大致相同——它也有一个巨大的寄存器集(如果内存允许的话,有几百个)。如果我没记错的话,对于如何使用寄存器来说,它更加宽松一些,但我希望至少在很多时候它的使用方式类似。
为什么这对你很重要?使用 stdarg.h 及其宏的目的是隐藏代码中调用约定的差异,因此它可以可移植地处理变量参数。
根据评论进行编辑:好的,现在我明白你在做什么(至少足以改进答案)。鉴于您(显然)已经拥有处理默认 ABI 中的变化的代码,事情就更简单了。这只留下了一个问题:可变参数函数是否始终使用“默认 ABI”,无论手头的平台是什么。由于“stdcall”和“default”是唯一的选项,我认为答案是肯定的。例如,在 Windows 上,wsprintf 和 wprintf 打破了经验法则,并使用 cdecl 调用约定而不是 stdcall。
The calling convention has to be one where the caller clears the arguments from the stack (because the callee doesn't know what will be passed).
That doesn't necessarily correspond to what Microsoft calls "__cdecl" though. Just for example, on a SPARC, it'll normally pass the arguments in registers, because that's how the SPARC is designed to work -- its registers basically act as a call stack that gets spilled to main memory if the calls get deep enough that they won't fit into register anymore.
Though I'm less certain about it, I'd expect roughly the same on IA64 (Itanium) -- it also has a huge register set (a couple hundred if memory serves). If I'm not mistaken, it's a bit more permissive about how you use the registers, but I'd expect it to be used similarly at least a lot of the time.
Why does this matter to you? The point of using stdarg.h and its macros is to hide differences in calling convention from your code, so it can work with variable arguments portably.
Edit, based on comments: Okay, now I understand what you're doing (at least enough to improve the answer). Given that you already (apparently) have code to handle the variations in the default ABI, things are simpler. That only leaves the question of whether variadic functions always use the "default ABI", whatever that happens to be for the platform at hand. With "stdcall" and "default" as the only options, I think the answer to that is yes. Just for example, on Windows,
wsprintf
andwprintf
break the rule of thumb, and uses cdecl calling convention instead of stdcall.确定这一点的最明确的方法是分析调用约定。为了使可变参数函数正常工作,您的调用约定需要几个属性:
printf
的第一个参数,即格式规范。此外,变量参数列表本身的地址也必须从已知位置派生。)stdcall
不起作用,因为被调用者负责将参数从堆栈中弹出。在旧的 16 位 Windows 时代,pascal
无法工作,因为它将参数从左到右压入堆栈。当然,正如其他答案所提到的,许多平台在调用约定方面不给您任何选择,使得这个问题与这些平台无关。
The most definitive way that you can determine this is to analyze the calling conventions. For variadic functions to work, your calling convention needs a couple of attributes:
printf
, the format specification. Also, the address of the variable argument list itself must also be derived from a known location.)stdcall
won't work because the callee is responsible for popping parameters off the stack. In the old 16-bit Windows days,pascal
wouldn't work because it pushed parameters onto the stack from left to right.Of course, as the other answers have alluded to, many platforms don't give you any choice in terms of calling convention, making this question irrelevant for those ones.
考虑 x86 系统上的以下函数:
void __stdcall Something(char *, ...);
该函数将自身声明为 __stdcall,这是一个被调用者干净的约定。但是可变参数函数不能被调用者清理,因为被调用者不知道传递了多少参数,因此它不知道应该清理多少个参数。
Microsoft Visual Studio C/C++ 编译器通过以静默方式将调用约定转换为 __cdecl 来解决此冲突,对于不采用隐藏 this 参数的函数,这是唯一受支持的可变参数调用约定。
为什么这种转换会悄无声息地进行而不是生成警告或错误?
我的猜测是,这是为了使编译器选项/Gr(将默认调用约定设置为 __fastcall)和/Gz(将默认调用约定设置为 __stdcall)不那么烦人。
将可变参数函数自动转换为 __cdecl 意味着您只需将 /Gr 或 /Gz 命令行开关添加到编译器选项中,所有内容仍将编译并运行(仅使用新的调用约定)。
另一种看待这个问题的方法不是将编译器视为将可变参数__stdcall 转换为__cdecl 而是简单地说“对于可变参数函数,__stdcall 是调用者干净的。”
单击此处
Consider the following function on an x86 system:
void __stdcall something(char *, ...);
The function declares itself as __stdcall, which is a callee-clean convention. But a variadic function cannot be callee-clean since the callee does not know how many parameters were passed, so it doesn’t know how many it should clean.
The Microsoft Visual Studio C/C++ compiler resolves this conflict by silently converting the calling convention to __cdecl, which is the only supported variadic calling convention for functions that do not take a hidden this parameter.
Why does this conversion take place silently rather than generating a warning or error?
My guess is that it’s to make the compiler options /Gr (set default calling convention to __fastcall) and /Gz (set default calling convention to __stdcall) less annoying.
Automatic conversion of variadic functions to __cdecl means that you can just add the /Gr or /Gz command line switch to your compiler options, and everything will still compile and run (just with the new calling convention).
Another way of looking at this is not by thinking of the compiler as converting variadic __stdcall to __cdecl but rather by simply saying “for variadic functions, __stdcall is caller-clean.”
click here
AFAIK,调用约定的多样性是 x86 上的 DOS/Windows 所独有的。大多数其他平台都有操作系统附带的编译器并标准化了约定。
AFAIK, the diversity of calling conventions is unique to DOS/Windows on x86. Most other platforms had compilers come with the OS and standardize the convention.
您的意思是“MSVC 支持的平台”还是一般规则?即使您将自己限制在 MSVC 支持的平台上,您仍然会遇到类似 IA64 和 AMD64 其中只有“一个”调用约定,该调用约定称为
__stdcall
,但它肯定与您得到的__stdcall
不同在 x86 上。Do you mean 'platforms supported by MSVC" or as a general rule? Even if you confine yourself to the platforms supported by MSVC, you still have situations like IA64 and AMD64 where there is only "one" calling convention, and that calling convention is called
__stdcall
, but it's certainly not the same__stdcall
you get on x86.