我注意到va_arg
宏存在一些潜在问题,该宏用于从可变参数函数接收未命名参数。请考虑以下简化示例:
#include <stdio.h>
#include <stdarg.h>
void foo(int n, ...)
{
va_list ap;
const char *s;
va_start(ap, n);
for (s = va_arg(ap, const char *); *s != ' '; s++)
putchar(*s);
va_end(ap);
}
int main(void)
{
char str[] = "xyz";
foo(1, str);
return 0;
}
va_arg
宏的参考指出(强调我的):
如果在 ap 中没有更多参数时调用 va_arg,或者如果 AP 中下一个参数的类型(升级后)不兼容 对于 T,行为是未定义的(...
我的理解是const char *
和char *
类型不兼容。当char
数组以char
指针的形式传递给foo
时(默认情况下参数提升保持不变),则假设 const 限定指针的表达式:
s = va_arg(ap, const char *)
可以调用"技术"UB。当arr
被定义为数组const
并且参数被接收为char *
,以及其他类型时,也会出现同样的情况,例如int *
和const int *
.
C11 语言标准的第 6.2.7 节定义了类型兼容性,第 6.7.6.1 节进一步指定了指针声明符,第 6.7.3 节定义了类型限定符。 后者(约束10)说:
对于两个合格的类型兼容,两者都应具有 兼容类型的相同限定版本;类型的顺序 说明符或限定符列表中的限定符不影响 指定的类型。
此外,第6.7.6.1节说:
对于要兼容的两种指针类型,两者都应具有相同的限定条件,并且两者都应是指向兼容类型的指针。
根据这两个,我知道const char*
和char*
不兼容(因为const char
和char
不兼容)。 根据第 7.16.1.1 节中要求类型兼容性的va_arg
定义(有几个例外在这里不适用),我相信在这种情况下您确实有未定义的行为。
现在,如果我们停止扮演律师的角色,我不认为将char*
视为const char*
---只会限制法律操作是有害的。 因此,我认为va_arg
的规范过于保守。
n1570/6.2.5p28 似乎表明这在实践中应该没问题:
。同样,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求...
由于char
与自身兼容,因此指向 char 的指针将与指向 const char 的指针具有相同的表示形式。由于变量参数函数本质上假定每个后续参数的表示形式,因此这在最初看起来定义良好。
至于相反的方式,如果你使用指向 char 的指针来修改 const char,那确实是每个 n1570/6.7.3p6 的 UB:
如果尝试通过使用具有非常量限定类型的左值来修改使用常量限定类型定义的对象,则行为是未定义的。
要从您在评论中链接到的答案继续分析,n1570/6.7.6.1p2:
对于要兼容的两种指针类型,两者都应具有相同的限定条件,并且两者都应是指向兼容类型的指针。
由于这两种指针类型本身都是非常量指针,因此它们具有相同的限定性,因此剩下的唯一问题是const char
是否与char
兼容以达到我们的目的。
而且看起来他们不是......正如尼基指出的那样。因此,一方面,这可以解释为UB。
是的,它们不兼容,当实际类型为 char* 时,将 va_arg 与 const char* 类型一起使用,反之亦然,是未定义的行为。
类型仅与自身兼容1。
此外,关于限定符的部分(const 是一个)支持此2。
(引自ISO/IEC 9899:201x)
1(6.2.7 兼容型和复合型1)
如果类型相同,则两种类型具有兼容类型
2(6.7.3 类型限定符 10)
对于要兼容的两个限定类型,两个类型都应具有相同的限定版本 兼容类型;说明符或限定符列表中的类型限定符的顺序 不影响指定的类型。