我们可以将va_arg与工会一起使用吗?

6.7.2.1我的C99标准草案第14段有关于工会和指针的说法(强调,一如既往地增加):

联合的大小足以容纳其中最大的成员。 最多一个成员的值可以随时存储在union对象中。 指向适当转换的并集对象的指针指向其每个成员(或者如果成员是位字段,则指向它所在的单位),反之亦然。

一切都很好,这意味着执行类似下面的操作是合法的,将signed或unsigned int复制到union中,假设我们只想将其复制到相同类型的数据中:

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_startva_copy宏初始化(没有为va_copy调用va_end宏)。 每次调用va_arg宏都会修改ap以便依次返回连续参数的值。 参数type应该是指定的类型名称,以便可以通过将*type来获得指向具有指定类型的对象的指针的type 。 如果没有实际的下一个参数, 或者type与实际的下一个参数的类型不兼容(根据默认参数提升而提升),则行为是未定义的,除了以下情况:

-one type是有符号整数类型,另一种类型是相应的无符号整数类型,并且该值可在两种类型中表示;

-one类型是指向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所有元素都是相同的大小,这是允许的与varargs? 或者只允许使用指针?

在实践中,我希望这段代码几乎不会失败,但我想知道它是否定义了行为。 我目前的猜测似乎是没有定义,但这看起来非常愚蠢。

你有几件事情。

指向适当转换的并集对象的指针指向其每个成员(或者如果成员是位字段,则指向它所在的单位),反之亦然。

这并不意味着类型是兼容的。 实际上,它们并不兼容。 所以下面的代码是错误的:

 func(0, 1); // undefined behavior 

如果你想通过一个联盟,

 func(0, (union ints){ .u = BLAH }); 

您可以通过编写代码进行检查,

 union ints x; x = 1; 

GCC在编译时给出“错误:赋值中的不兼容类型”消息。

但是,在这两种情况下,大多数实现都“可能”做正确的事情。 还有一些其他问题……

 union ints { int i; unsigned u; }; int i = 4; union ints is = *(union ints *)&i; // Invalid int j = is.i; // legal unsigned k = is.u; // also legal (see note) 

使用非实际类型*(uinon ints *)&i取消引用类型地址时的行为有时是未定义的(查找引用,但我很确定这一点)。 但是,在C99中,允许访问除最近存储的union成员之外的union成员(或者它是C1x?),但该值是实现定义的,可能是陷阱表示。

关于通过工会打字的类型:正如Pascal Cuoq所说,实际上TC3定义了访问最近存储的联合元素之外的联合元素的行为。 TC3是C99的第三次更新。 好消息是TC3的这一部分真正编纂了现有的实践 – 所以在TC3之前将其视为C的事实上的一部分。

由于标准说:

参数类型应该是指定的类型名称,以便可以通过将*固定到类型来获得指向具有指定类型的对象的指针的类型。

对于union ints ,这种条件是满足的。 因为union ints *是指向union ints的指针的完美表示,所以在该句子中没有任何东西可以防止它被用于收集作为联合推送到堆栈的值。

如果你作弊并尝试传递一个普通的intunsigned int来代替一个union,那么你将调用未定义的行为。 因此,您可以使用:

 union ints u1 = ...; func(0, (union ints) { .i = 0 }); func(1, (union ints) { .u = UINT_MAX }); func(2, u1); 

你不能使用:

 func(1, 0); 

参数不是联合类型。

我不明白为什么你认为代码在实践中永远不会失败。 它会在寄存器传递整数类型的任何实现上失败,但聚合类型(即使很小)在堆栈上传递,我在标准中看不到任何禁止此类实现的内容。 包含int的联合不是与int兼容的类型,即使它们的大小相同。

回到你的第一个代码片段,它也有一个问题:

 union ints is = *(union ints *)&i; 

这是一种别名冲突并调用未定义的行为。 你可以通过使用memcpy来避免它,我想那时它是合法的..

我对你的评论有点困惑:

 unsigned k = is.u; // not so much 

由于值4以有符号和无符号类型表示,因此这应该是合法的,除非作为特殊情况特别禁止。

如果这不能回答你的问题,也许你可以详细说明你试图解决的问题(尽管是理论上的)。