Dar*_*ghi 11 c pointers unions
通过指针访问union成员(如下例所示)会导致C99中的未定义行为吗?意图似乎很清楚,但我知道有关于别名和联盟的一些限制.
union { int i; char c; } u;
int *ip = &u.i;
char *ic = &u.c;
*ip = 0;
*ic = 'a';
printf("%c\n", u.c);
Run Code Online (Sandbox Code Playgroud)
pax*_*blo 15
它是未指定的(略微不同于undefined)行为,以通过除最后写入的元素之外的任何元素访问并集.这在C99附件J中有详细说明:
以下是未指定的:
:
比存储到(6.2.6.1)的最后一个以外的联盟成员的值.
但是,由于您c通过指针写入,然后读取c,这个特定的示例是明确定义的.你如何写元素并不重要:
u.c = 'a'; // direct write.
*(&(u.c)) = 'a'; // variation on yours, writing through element pointer.
(&u)->c = 'a'; // writing through structure pointer.
Run Code Online (Sandbox Code Playgroud)
在评论中提出的一个问题似乎与此相矛盾,至少看似这样.用户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 <stdio.h>
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;
}
Run Code Online (Sandbox Code Playgroud)
它在不同的编译器上输出不同的值.但是,我认为这是因为它实际上违反了规则,因为它写入成员f然后读取成员i,如附件J所示,这是未指定的.
这里是一个脚注82 6.5.2.3规定:
如果用于访问union对象的内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将重新解释为新类型中的对象表示.
但是,由于这似乎违反了附件J的注释,并且它是处理表单表达式的部分的脚注x.y,它可能不适用于通过指针访问.
别名被认为是严格的主要原因之一是允许编译器有更多的优化空间.为此,该标准要求将未写入的内存与未写入的内容相对应.
举例来说,考虑提供的功能:
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!
}
Run Code Online (Sandbox Code Playgroud)
实现可以自由地假设,因为你不应该别名内存,up->i并且*fp是两个不同的对象.因此可以自由地假设您在up->i设置之后没有更改其值,123因此它可以简单地返回123而无需再次查看实际的变量内容.
相反,您将指针设置语句更改为:
up->f = 2.0;
Run Code Online (Sandbox Code Playgroud)
然后,这将使脚注82适用,并且返回的值将是浮点的重新解释为整数.
我不认为这个问题的问题是因为你的写作然后读取相同的类型,因此别名规则不起作用.
值得注意的是,未指定的行为不是由函数本身引起的,而是由它调用它:
union u up;
int x = someFunc (&u, &(up.f)); // <- aliasing here
Run Code Online (Sandbox Code Playgroud)
如果你是这样称呼它:
union u up;
float down;
int x = someFunc (&u, &down); // <- no aliasing
Run Code Online (Sandbox Code Playgroud)
那不是问题.
不,不会,但是您需要跟踪加入工会的最后一种类型是什么。如果我要颠倒你int和char任务的顺序,那将是一个非常不同的故事:
#include <stdio.h>
union { int i; char c; } u;
int main()
{
int *ip = &u.i;
char *ic = &u.c;
*ic = 'a';
*ip = 123456;
printf("%c\n", u.c); /* trying to print a char even though
it's currently storing an int,
in this case it prints '@' on my machine */
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编辑:关于为什么它可能已经打印64('@')的一些解释。
123456的二进制表示形式是0001 1110 0010 0100 0000。
对于64,它是0100 0000。
您会看到前8位是相同的,并且由于printf指示读取前8位,因此它仅打印相同的内容。