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