当va_arg收到指针参数时,const不匹配是否会调用UB?

时间:2017-02-21 21:20:17

标签: c language-lawyer variadic-functions

我注意到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 != '\0'; 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数组以foo指针的形式传递给char时(默认参数提升不变),那么表达式假定为const限定指针:

s = va_arg(ap, const char *)

可以调用“技术”UB。将arr定义为const数组,并将参数作为char *接收,以及其他类型,例如{...}}时,会出现同样的情况。 int *const int *

3 个答案:

答案 0 :(得分:3)

n1570/6.2.5p28似乎暗示这在实践中应该没问题:

  

...同样,指向兼容类型的合格或非限定版本的指针应具有相同的表示和对齐要求......

由于char与自身兼容,因此指向char的指针将具有与指向const char的指针相同的表示形式。由于变量参数函数基本上假定每个后续参数的表示,因此首先定义良好。

至于另一种方式,如果你使用一个指向char的指针来修改一个const char,那确实是每n1570/6.7.3p6个UB:

  

如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。

要继续从评论中链接到的答案进行分析,n1570/6.7.6.1p2

  

要使两个指针类型兼容,两者都应具有相同的限定条件,并且两者都应是兼容类型的指针。

由于两个指针类型本身都是非常量的,因此它们具有相同的限定条件,唯一的问题是const char是否与char兼容。

似乎他们不是......正如尼基指出的那样。所以一方面,这可以解释为UB。

答案 1 :(得分:3)

C11 language standard的第6.2.7节定义类型兼容性,第6.7.6.1节进一步为指针声明符指定它,第6.7.3节指定类型限定符。后者(约束10)说:

  

要兼容两种合格类型,两者都应具有   相同类型的兼容类型;类型的顺序   说明符或限定符列表中的限定符不会影响   指定的类型。

另外,第6.7.6.1节说:

  

要使两个指针类型兼容,两者都应具有相同的限定条件,并且两者都应是兼容类型的指针。

根据这两个,我了解const char*char*不兼容(因为const charchar不兼容)。根据7.16.1.1节中va_arg的定义,它要求类型兼容性(这里有一些不适用的例外),我相信在这种情况下确实你有未定义的行为。

现在,如果我们停止扮演律师,我不明白将char*视为const char*会如何有害 - 这只会限制合法行动。因此,我认为va_arg的规范过于保守。

答案 2 :(得分:1)

是的,当实际类型为char *时,它们不兼容并且使用类型为const char *的va_arg,反之亦然,是未定义的行为。

类型只与自身 1 兼容。

此外,关于限定符的部分,const为1,支持 2

(引自ISO / IEC 9899:201x)

1 (6.2.7兼容型和复合型1)
如果类型相同,则两种类型具有兼容类型

2 (6.7.3类型限定符10)
要使两种合格类型兼容,两者都应具有相同的合格版本 兼容类型;说明符或限定符列表中类型限定符的顺序 不会影响指定的类型。