通过指针访问C联合成员

通过指针访问union成员(如下例所示)会导致C99中的未定义行为吗? 意图似乎很清楚,但我知道在别名和工会方面存在一些限制。

union { int i; char c; } u; int *ip = &u.i; char *ic = &u.c; *ip = 0; *ic = 'a'; printf("%c\n", uc); 

除了上次写入的元素之外的任何元素访问并集时, 未指定 (略微不同于undefined)行为。 这在C99附件J中有详细说明:

以下是未指定的:

联合成员的值不是存储在(6.2.6.1)中的最后一个成员。

但是,由于您通过指针写入c ,然后读取c ,这个特定的例子已经很好地定义了。 你如何写元素并不重要:

 uc = 'a'; // direct write. *(&(uc)) = 'a'; // variation on yours, writing through element pointer. (&u)->c = 'a'; // writing through structure pointer. 

在评论中提出了一个似乎与此相矛盾的问题,至少看似这样。 用户davmac提供示例代码:

 // Compile with "-O3 -std=c99" eg: // clang -O3 -std=c99 test.c // gcc -O3 -std=c99 test.c // On clang v3.5.1, output is "123" // On gcc 4.8.4, output is "1073741824" // // Different outputs, so either: // * program invokes undefined behaviour; both compilers are correct OR // * compiler vendors interpret standard differently OR // * one compiler or the other has a bug #include  union u { int i; float f; }; int someFunc(union u * up, float *fp) { up->i = 123; *fp = 2.0; // does this set the union member? return up->i; // then this should not return 123! } int main(int argc, char **argv) { union u uobj; printf("%d\n", someFunc(&uobj, &uobj.f)); return 0; } 

它在不同的编译器上输出不同的值。 但是,我认为这是因为它实际上违反了规则,因为它写入成员f然后读取成员i ,如附件J所示,这是未指定的。

6.5.2.3有一个脚注82,其中指出:

如果用于访问union对象的内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将重新解释为新类型中的对象表示。

但是,由于这似乎违反了附件J的注释,并且它是处理xy表达式的部分的脚注,它可能不适用于通过指针访问。

别名被认为是严格的主要原因之一是允许编译器有更多的优化空间。 为此,该标准要求将未写入类型的内存与未写入的内容相对应。

举例来说,考虑提供的function:

 int someFunc(union u * up, float *fp) { up->i = 123; *fp = 2.0; // does this set the union member? return up->i; // then this should not return 123! } 

实现可以自由地假设,因为你不应该使用别名内存, up->i*fp是两个不同的对象。 因此可以自由地假设在将其设置为123之后不会更改up->i的值,因此它可以简单地返回123而无需再次查看实际的变量内容。

相反,您将指针设置语句更改为:

 up->f = 2.0; 

然后,这将使脚注82适用,并且返回的值将是浮点的重新解释为整数。

我不认为这个问题的问题是因为你的写作然后读取相同的类型,因此别名规则不起作用。


有趣的是,未指定的行为不是由函数本身引起的而是由它调用它:

 union u up; int x = someFunc (&u, &(up.f)); // <- aliasing here 

如果你是这样称呼它:

 union u up; float down; int x = someFunc (&u, &down); // <- no aliasing 

不是问题。

不,它不会,但你需要跟踪你输入联盟的最后一种类型。 如果我要颠倒你的intchar赋值的顺序,那将是一个非常不同的故事:

 #include  union { int i; char c; } u; int main() { int *ip = &u.i; char *ic = &u.c; *ic = 'a'; *ip = 123456; printf("%c\n", uc); /* trying to print a char even though it's currently storing an int, in this case it prints '@' on my machine */ return 0; } 

编辑:为什么它可能打印64(’@’)的一些解释。

123456的二进制表示为0001 1110 0010 0100 0000。

对于64,它是0100 0000。

您可以看到前8位是相同的,因为printf被指示读取前8位,所以它只打印那么多。

它不是UB的唯一原因是因为你很幸运/不幸的是为其中一种类型选择了char ,而字符类型可以在C中使用别名。如果类型是例如intfloat ,则通过指针进行访问将是别名违规和未定义的行为。 对于通过工会的直接访问,该行为被认为是对缺陷报告283的解释的一部分:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm

当然,您仍需要确保用于写入的类型的表示也可以解释为稍后用于读取的类型的有效(非陷阱)表示。