为什么 C 不允许具有可变长度参数列表的函数,例如:
void f(...)
{
// do something...
}
我认为要求 varargs 函数必须具有命名参数的动机是为了统一va_start
. 为了便于实施,va_start
采用最后一个命名参数的名称。使用典型的 varargs 调用约定,并根据存储参数的方向,将在 address orva_arg
处找到第一个 vararg ,加上或减去一些填充以确保对齐。(¶meter_name) + 1
(first_vararg_type*)(¶meter_name) - 1
我不认为该语言不能支持没有命名参数的可变参数函数有什么特别的原因。在这样的函数中必须有另一种形式的va_start
使用,它必须直接从堆栈指针获取第一个可变参数(或者是迂腐的帧指针,这实际上是堆栈指针在函数上的值入口,因为函数中的代码很可能已经从函数入口移动了 sp)。原则上这是可能的——任何实现都应该在某种程度上以某种方式访问堆栈[*]——但这对一些实现者来说可能很烦人。一旦您知道可变参数调用约定,您通常可以在va_
没有任何其他特定于实现的知识的情况下实现宏,这将需要还知道如何直接获取调用参数。我之前在仿真层中实现了这些可变参数宏,这会让我很恼火。
此外,没有命名参数的可变参数函数没有太多实际用途。varargs 函数没有语言特性来确定变量参数的类型和数量,因此被调用者必须知道第一个 vararg 的类型才能读取它。因此,您不妨将其设为具有类型的命名参数。在printf
和朋友中,第一个参数的值告诉函数可变参数的类型是什么,以及它们有多少。
我想理论上被调用者可以查看一些全局变量来弄清楚如何读取第一个参数(以及是否有一个),但这很讨厌。我当然不会不遗余力地支持这一点,并且添加一个va_start
带有额外实施负担的新版本是不合我意的。
[*] 或者如果实现不使用堆栈,则将其用于传递函数参数的任何内容。
使用可变长度参数列表,您必须声明第一个参数的类型——这是语言的语法。
void f(int k, ...)
{
/* do something */
}
会工作得很好。然后,您必须在函数内部使用va_list
、va_start
、va_end
等来访问各个参数。
C 确实允许可变长度参数,但您需要为此使用va_list、va_start、va_end 等。你觉得printf和朋友们是怎么实现的?也就是说,我建议不要这样做。您通常可以使用数组或结构作为参数更干净地完成类似的事情。
玩弄它,做出了这个不错的实现,我认为有些人可能想要考虑。
template<typename T>
void print(T first, ...)
{
va_list vl;
va_start(vl, first);
T temp = first;
do
{
cout << temp << endl;
}
while (temp = va_arg(vl, T));
va_end(vl);
}
它确保您有一个变量最小值,但允许您以一种干净的方式将它们全部放在一个循环中。
C 不能接受 void f(...) 没有根本原因。可以,但是这个 C 特性的“设计者”决定不这样做。
我对他们的动机的猜测是,允许 void f(...) 将需要更多的“隐藏”代码(可以被视为运行时)而不是不允许它:为了区分 f() 和 f(arg ) (以及其他),C 应该提供一种方法来计算给出了多少个 args,这需要更多生成的代码(并且可能需要一个新的关键字或一个特殊的变量,比如说“nargs”来检索计数),而 C 通常尽量做到极简主义。
...
允许没有参数,即:对于int printf(const char *format, ...);
语句
printf("foobar\n");
已验证。
如果您不要求至少 1 个参数(应该用于检查更多参数),则函数无法“知道”它是如何调用的。
所有这些陈述都是有效的
f();
f(1, 2, 3, 4, 5);
f("foobar\n");
f(qsort);