最后命名参数不是函数还是数组?

时间:2009-08-30 08:10:34

标签: c++ c arrays variadic-functions

这个问题是关于vararg函数,以及它们的最后命名参数,在省略号之前:

void f(Type paramN, ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

我正在阅读C标准,并发现va_start宏的以下限制:

  

参数parmN是函数定义中变量参数列表中最右边参数的标识符(就在...之前的参数)。如果使用寄存器存储类,函数或数组类型声明参数parmN,或者使用与应用默认参数升级后产生的类型不兼容的类型,则行为未定义。

我想知道为什么以下代码的行为未定义

void f(int paramN[], ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

并且未定义以下

void f(int *paramN, ...) {
  va_list ap;
  va_start(ap, paramN);
  va_end(ap);
}

宏可以通过纯C代码实现。但纯C代码无法确定paramN是否被声明为数组或指针。在这两种情况下,参数的类型都被调整为指针。功能类型参数也是如此。

我想知道:这个限制的理由是什么?当内部进行这些参数调整时,某些编译器是否存在实现此问题的问题? (C ++也说明了相同的未定义行为 - 所以我的问题是关于C ++的问题)。

5 个答案:

答案 0 :(得分:6)

对寄存器参数或函数参数的限制可能类似于:

  • 您不能使用register存储类获取变量的地址。
  • 函数指针有时与指向对象的指针完全不同。例如,它们可能比指向对象的指针大(您无法将函数指针可靠地转换为对象指针并再次返回),因此向函数指针的地址添加一些固定数字可能无法使您进入下一个参数。如果通过向va_start()的地址添加一些固定数量并且函数指针大于对象指针来实现va_arg()和/或paramN,则计算将最终得到对象的错误地址va_arg()返回。这似乎不是实现这些宏的好方法,但可能有平台有(甚至需要)这种类型的实现。

我无法想到阻止允许数组参数会出现什么问题,但PJ Plauger在他的“标准C库”一书中这样说:

  

<stdarg.h>中定义的宏施加的一些限制似乎不必要地严重。对于某些实现,它们是。然而,每个都被引入,以满足至少一个严重的C实现的需要。

我想,很少有人比普劳格更了解C库的来龙去脉。我希望有人可以用一个实际的例子回答这个具体问题;我认为这将是一个有趣的琐事。

新信息:


“国际标准的基本原理 - 编程语言 - C”对此va_start()说:

  

parmN的{​​{1}}参数旨在帮助实现者编写。{1}}   完全用C语言定义符合va_start的宏,即使使用前C89编译器(例如,通过获取参数的地址)。对va_start参数声明的限制遵循允许这种实现的意图,如同应用&amp;如果参数的声明不符合这些限制,则参数名称的运算符可能不会产生预期的结果。

这并不能帮助我限制数组参数。

答案 1 :(得分:3)

这不是未定义的。请记住,当参数声明为int paramN[]时,实际参数类型仍将立即衰减到int* paramN(例如,如果您将typeid应用于{{ 1}})。

我必须承认,我不确定规范中的这个位是什么,考虑到你不能首先拥有函数或数组类型的参数(因为它们会指针衰减)。

答案 2 :(得分:2)

我找到了另一个相关引文,from Dinkumware

  

最后一个参数不能有   注册存储类,它必须   有一个不被改变的类型   翻译。它不能有:

* an array type
* a function type
* type float
* any integer type that changes when promoted
* a reference type [C++ only]

显然,问题恰恰在于参数的传递方式与声明的方式不同。有趣的是,它们也禁止浮动和短路,即使这些应该得到标准的支持。

作为一个假设,可能是某些编译器在这些参数上正确执行sizeof时遇到问题。例如。可能是,对于

int f(int x[10])
{
        return sizeof(x);
}

某些(buggy)编译器将返回10*sizeof(int),从而违反va_start实现。

答案 3 :(得分:1)

我只能猜测register限制是为了简化库/编译器的实现 - 它消除了一个让他们担心的特殊情况。

但我对阵列/功能限制一无所知。如果它只是在C ++标准中,我会猜测存在一些模糊的模板匹配场景,其中T[]类型的参数与类型T*的参数之间的差异有所不同,正确处理这会使va_start等复杂化。但由于这个条款也出现在C标准中,显然这个解释被排除了。

我的结论:对标准的疏忽。可能的情况:某些预标准C编译器实现的类型T[]T*的参数不同,C标准委员会中该编译器的发言人已将上述限制添加到标准中;该编译器后来变得过时了,但是没有人觉得这些限制足以引起更新标准。

答案 4 :(得分:1)

C ++ 11说:

  

[n3290: 13.1/3]: [..]参数声明仅在a中有所不同   指针*与数组[]是等价的。就是阵列   声明被调整为成为指针声明。 [..]

和C99也是:

  

[C99: 6.7.5.3/7]:参数声明为''数组类型''应调整为''限定指针   type'',其中类型限定符(如果有)是在[和]中指定的类型   数组类型推导。 [..]

你说:

  

但纯C代码无法确定paramN是否被声明为数组或指针。在这两种情况下,参数的类型都会调整为指针。

是的,所以你向我们展示的两段代码没有区别。两者都将paramN声明为指针;实际上根本没有数组类型。

那么为什么两者在UB方面会有区别呢?

你引用的那段话......

  

参数parmN是函数定义中变量参数列表中最右边参数的标识符(就在...之前的参数)。 如果使用寄存器存储类,函数或数组类型声明参数parmN,或者使用与应用默认参数升级后产生的类型不兼容的类型,则行为未定义。

...不适用于 ,如预期的那样。