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

这个问题是关于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是否被声明为数组或指针。 在这两种情况下,参数的类型都被调整为指针。 function类型参数也是如此。

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

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

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

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

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

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

新信息:


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

va_startparmN参数旨在帮助实现者完全用C语言编写符合标准的va_start宏的定义,甚至使用pre-C89编译器(例如,通过获取参数的地址)。 对parmN参数声明的限制遵循允许这种实现的意图,因为如果参数的声明不符合这些限制,将&运算符应用于参数名称可能不会产生预期的结果。

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

这不是未定义的。 请记住,当参数声明为int paramN[] ,实际参数类型仍将立即衰减到int* paramN (例如,如果将typeid应用于paramN ,则在C ++中可见)。

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

我从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实现。

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

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

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

C ++ 11说:

[n3290: 13.1/3]: [..]仅在指针*与数组[]之间不同的参数声明是等效的。 也就是说,调整数组声明以成为指针声明。 [..]

和C99也是:

[C99: 6.7.5.3/7]:参数声明为”数组类型”应调整为”限定指向类型”,其中类型限定符(如果有的话)是[和]数组类型派生。 [..]

你说:

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

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

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

你引用的那段话……

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

…… 既不适用, 也不适用。