这个问题是关于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 ++的问题)。
答案 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,或者使用与应用默认参数升级后产生的类型不兼容的类型,则行为未定义。强>
...不适用于 ,如预期的那样。