6.7.2.1我的C99标准草案第14段有关于工会和指针的说法(强调,一如既往地增加):
联盟的大小足以容纳其中最大的成员。 at的值 大多数成员可以随时存储在union对象中。 指向a的指针 适当转换的联合对象指向其每个成员(或者如果成员有点 - 字段,然后到它所在的单位),反之亦然。
一切都很好,这意味着执行类似下面的操作以将有符号或无符号的int复制到联合中是合法的,假设我们只想将其复制到相同类型的数据中:
union ints { int i; unsigned u; };
int i = 4;
union ints is = *(union ints *)&i;
int j = is.i; // legal
unsigned k = is.u; // not so much
7.15.1.1第2段有这样的说法:
va_arg
宏扩展为具有指定类型和值的表达式 通话中的下一个参数。参数ap
应该由。初始化va_start
或va_copy
宏(没有为sameap调用va_end
宏)。每次调用va_arg
宏都会修改ap
,以便依次返回连续参数的值。参数type
应该是指定的类型名称,以便只需通过将*
后缀为type
即可获得指向具有指定类型的对象的指针类型。如果没有实际的下一个参数,或者如果type与实际的下一个参数的类型不兼容(根据默认参数提升促销),行为是未定义的,除了以下情况:-one类型是有符号整数类型,另一种类型是相应的无符号整数 类型,值可以在两种类型中表示;
-one type是指向void的指针,另一个是指向字符类型的指针。
我不打算引用关于默认参数促销的部分。我的问题是:这是定义的行为:
void func(int i, ...)
{
va_list arg;
va_start(arg, i);
union ints is = va_arg(arg, union ints);
va_end(arg);
}
int main(void)
{
func(0, 1);
return 0;
}
如果是这样的话,它似乎是一个巧妙的技巧来克服“并且值与两种类型兼容”的有符号/无符号整数转换要求(虽然这种方式很难合法地做任何事情)。如果没有,在这种情况下使用unsigned
似乎是安全的,但是如果union
中有更多不兼容类型的元素会怎样?如果我们可以保证我们不会按元素访问union(即我们只是将它复制到另一个union
或存储空间,我们将其视为union
)并且联合的所有元素大小相同,这是否允许使用varargs?或者只允许使用指针?
在实践中,我希望这段代码几乎不会失败,但我想知道它是否定义了行为。我目前的猜测似乎是没有定义,但这看起来非常愚蠢。
答案 0 :(得分:3)
答案 1 :(得分:2)
由于标准说:
参数类型应为指定的类型名称,以便只需通过将*固定为类型即可获得指向具有指定类型的对象的指针类型。
对于union ints
,满足该条件。由于union ints *
是指向union ints
的指针的完美表示,因此该句子中没有任何内容可以阻止它被用于收集作为联合推送到堆栈的值。
如果你作弊并尝试传递普通的int
或unsigned int
代替联合,那么你将调用未定义的行为。因此,您可以使用:
union ints u1 = ...;
func(0, (union ints) { .i = 0 });
func(1, (union ints) { .u = UINT_MAX });
func(2, u1);
您无法使用:
func(1, 0);
参数不是联合类型。
答案 2 :(得分:0)
我不明白为什么你认为代码在实践中永远不会失败。它会在寄存器传递整数类型的任何实现上失败,但聚合类型(即使很小)在堆栈上传递,我在标准中看不到任何禁止此类实现的内容。包含int
的联合不是与int
兼容的类型,即使它们的大小相同。
回到你的第一个代码片段,它也有一个问题:
union ints is = *(union ints *)&i;
这是别名违规并调用未定义的行为。您可以使用memcpy
来避免它,我想那时它是合法的。
我对你的评论感到有点困惑:
unsigned k = is.u; // not so much
由于值4在有符号和无符号类型中都有表示,因此这应该是合法的,除非它被特别禁止作为一种特殊情况。
如果这不能回答你的问题,也许你可以详细说明你试图解决的问题(虽然是理论上的)。