为什么C语言中不允许使用void f(...)?

6

为什么C语言不允许使用可变长度参数列表的函数,例如:

void f(...)
{
    // do something...
}

预标准 C 允许这些函数。 - chux - Reinstate Monica
6个回答

10
我认为要求具有变长参数的函数必须有一个命名参数的动机是为了保持va_start的一致性。为了方便实现,va_start获取最后一个命名参数的名称。根据典型的变长参数调用约定和参数存储方向,va_arg将在地址(&parameter_name) + 1(first_vararg_type*)(&parameter_name) - 1(加减一些填充以确保对齐)处找到第一个vararg。
我不认为有任何特别的原因使语言无法支持没有命名参数的可变参数函数。针对这种函数,必须有一种替代形式的va_start,它必须直接从堆栈指针中获取第一个vararg(或者严谨地说,从帧指针中获取,因为函数中的代码很可能已经移动了sp)。原则上这是可行的 -- 任何实现都应该以某种方式访问堆栈[*],在某个层面上 -- 但这可能会令一些实施者感到恼火。一旦您知道了可变参数调用约定,通常可以实现va_宏而无需任何其他实现特定的知识,而这也需要知道如何直接获取调用参数。我之前在仿真层中实现了这些可变参数宏,这让我感到很烦。
此外,没有多少实际用途可以使用没有命名参数的变长参数函数。对于一个变长参数函数来说,没有语言特性可以确定可变参数的类型和数量,因此被调用者仍然必须知道第一个vararg的类型才能读取它。因此,最好将其作为带有类型的命名参数。在printf和朋友们中,第一个参数的值告诉函数varargs的类型以及它们的数量。

我认为在理论上,被调用的函数可以查看某些全局变量来确定如何读取第一个参数(以及是否有参数),但这样做相当麻烦。我肯定不会特意支持这种操作,并且增加一个新版本的va_start会给实现带来额外的负担。

[*]或者如果实现不使用堆栈,则使用其他方式传递函数参数。


1
Unix的<varargs.h>(标准设施的前身和启发者)仅允许可变参数。C语言的理念指出,在可变参数之前具有固定类型参数的可能性是标准化过程的一项创新。我还要注意到,C++允许没有固定类型参数,并且我所见过的唯一用途是在模板元编程上下文中。 - AProgrammer
我认为这里的关键洞察力在于“...因此被调用者必须知道第一个可变参数的类型才能读取它。” - caf

9

使用可变长度参数列表时,您必须声明第一个参数的类型-这是语言的语法要求。

void f(int k, ...)
{
    /* do something */
}

这将完美地工作。然后,您需要在函数内部使用va_listva_startva_end等来访问各个参数。


3
+1 是因为你是唯一一个读懂问题并解释为什么不允许的回答者。我赞同你的观点。 - user142019
@WTP:严格来说,“……因为这是第一个由实际阅读并明确表达的人给出的答案……” - sehe
3
“因为它的语法”并不是一个解释,而只是一个事实陈述。 - ShinTakezou
1
考虑蒙希豪森三难问题。 - Michael Foukarakis
2
@Aleks:你的回答只是一些信息,这是C语言中var arg列表用法的定义,而不是我的问题的答案。C++支持void f(...);没有问题! - Rajendra Uppal

1

C语言确实允许使用可变长度参数,但需要使用va_list、va_start、va_end等。你认为printf和相关函数是如何实现的呢?尽管如此,我仍建议不要使用它。通常情况下,你可以使用数组或结构体更清晰地完成类似的操作。


而且 va_start 需要一个函数参数名。 - lhf

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);
}

它确保您至少有一个变量,但允许您以清晰的方式将它们全部放入循环中。

0

在编程中,C语言没有内在原因不能接受void f(...)。它本来可以这样做,但是C特性的“设计者”决定不这样做。

我猜测他们这样做的动机是,允许void f(...)需要更多的“隐藏”代码(可以视为运行时),而不允许会更少:为了区分f()和f(arg)(以及其他情况),C应该提供一种计算参数个数的方法,这需要更多生成的代码(可能是一个新关键字或类似“nargs”的特殊变量来检索计数),而C通常尽可能地保持极简主义。


-2

...允许没有参数,例如:int printf(const char *format, ...);语句。

printf("foobar\n");

是有效的。

如果您不强制要求至少一个参数(应该用于检查更多参数),函数就无法“知道”它是如何被调用的。

所有这些语句都是有效的。

f();
f(1, 2, 3, 4, 5);
f("foobar\n");
f(qsort);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接