工会对这种使用是否严格遵守? [英] Is this use of unions strictly conforming?

查看:109
本文介绍了工会对这种使用是否严格遵守?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定代码:

  struct s1 {unsigned short x;}; 
struct s2 {unsigned short x;};
union s1s2 {struct s1 v1; struct s2 v2; };

static int read_s1x(struct s1 * p){return p-> x; }
static void write_s2x(struct s2 * p,int v){p-> x = v;}

int test(union s1s2 * p1,union s1s2 * p2,union s1s2 * p3)
{
if(read_s1x(& p1-> v1))
{
unsigned short temp;
temp = p3-> v1.x;
p3-> v2.x = temp;
write_s2x(& p2-> v2,1234);
temp = p3-> v2.x;
p3-> v1.x = temp;
}
返回read_s1x(& p1-> v1);
}
int test2(int x)
{
union s1s2 q [2];
q-> v1.x = 4321;
return test(q,q + x,q + x);
}
#include< stdio.h>
int main(void)
{
printf(%d \ n,test2(0));

$ / code>

整个程序中存在一个联合对象 - q 。其活动成员设置为 v1 ,然后设置为 v2 ,然后设置为 v1 。代码仅在 q.v1 中使用address-of运算符,或者在成员处于活动状态时产生的指针,同样 q.v2 。由于 p1 p2 p3 都一样类型,使用 p3-> v1 来访问 p1-> v1 p3-> v2 来存取 p2-> v2

我没有看到任何证明编译器无法输出1234的东西,但是包括clang和gcc在内的许多编译器都会生成输出4321的代码。我认为发生的事情是他们认为p3上的操作不会实际上改变内存中任何位的内容,它们可以完全忽略,但是我没有看到标准中的任何内容会证明忽略了使用 p3 的事实将数据从 p1-> v1 复制到 p2-> v2 ,反之亦然。



标准中是否有任何证明这种行为的理由,或者编译器是否不遵循它?

解决方案

我相信你的代码是一致的,在那里是GCC和Clang的 -fstrict-aliasing 模式的缺陷。



我无法找到正确的部分C标准,但是在为我编译C ++模式下的代码时会发生同样的问题,并且我确实找到了C ++标准的相关段落。在b ++标准中, ,[class.union] / 5定义了在联合访问表达式上使用operator = 时会发生什么。 C ++标准规定,当一个联合参与内置运算符 = 的成员访问表达式时,联合的活动成员被改变为参与表达式(如果类型有一个简单的构造函数,但是因为这是C代码,它确实有一个简单的构造函数)。
$ b 注意 write_s2x 不能更改联合的活动成员,因为联合不包含在赋值表达式中。你的代码不会假设发生这种情况,所以没关系。



即使我使用placement new 来显式更改哪个联合成员是活动的,这应该是编译器提示活动成员改变了,GCC仍然生成输出 4321 的代码。



这看起来像是一个GCC和Clang的错误,它假定活动联合成员的切换不会发生在这里,因为他们没有意识到 p1 p2 p3 都指向同一个对象。



GCC和Clang(以及几乎所有其他编译器)都支持对C / C ++的扩展,您可以在其中读取联合中不活动的成员(从而得到可能的垃圾值),但只有当您在成员访问表达式涉及工会。如果 v1 不是活动成员,则在此实现下 read_s1x 不会被定义为行为特定的规则,因为联合不在成员访问表达式中。但是因为 v1 是活跃成员,所以应该没关系。



这是一个复杂的情况,我希望我的分析是正确的,作为不是编译器维护者或其中一个委员会成员的人。


Given the code:

struct s1 {unsigned short x;};
struct s2 {unsigned short x;};
union s1s2 { struct s1 v1; struct s2 v2; };

static int read_s1x(struct s1 *p) { return p->x; }
static void write_s2x(struct s2 *p, int v) { p->x=v;}

int test(union s1s2 *p1, union s1s2 *p2, union s1s2 *p3)
{
  if (read_s1x(&p1->v1))
  {
    unsigned short temp;
    temp = p3->v1.x;
    p3->v2.x = temp;
    write_s2x(&p2->v2,1234);
    temp = p3->v2.x;
    p3->v1.x = temp;
  }
  return read_s1x(&p1->v1);
}
int test2(int x)
{
  union s1s2 q[2];
  q->v1.x = 4321;
  return test(q,q+x,q+x);
}
#include <stdio.h>
int main(void)
{
  printf("%d\n",test2(0));
}

There exists one union object in the entire program--q. Its active member is set to v1, and then to v2, and then to v1 again. Code only uses the address-of operator on q.v1, or the resulting pointer, when that member is active, and likewise q.v2. Since p1, p2, and p3 are all the same type, it should be perfectly legal to use p3->v1 to access p1->v1, and p3->v2 to access p2->v2.

I don't see anything that would justify a compiler failing to output 1234, but many compilers including clang and gcc generate code that outputs 4321. I think what's going on is that they decide that the operations on p3 won't actually change the contents of any bits in memory, they can just be ignored altogether, but I don't see anything in the Standard that would justify ignoring the fact that p3 is used to copy data from p1->v1 to p2->v2 and vice versa.

Is there anything in the Standard that would justify such behavior, or are compilers simply not following it?

解决方案

I believe that your code is conformant, and there is a flaw with the -fstrict-aliasing mode of GCC and Clang.

I cannot find the right part of the C standard, but the same problem happens when compiling your code in C++ mode for me, and I did find the relevant passages of the C++ Standard.

In the C++ standard, [class.union]/5 defines what happens when operator = is used on a union access expression. The C++ Standard states that when a union is involved in the member access expression of the built-in operator =, the active member of the union is changed to the member involved in the expression (if the type has a trivial constructor, but because this is C code, it does have a trivial constructor).

Note that write_s2x cannot change the active member of the union, because a union is not involved in the assignment expression. Your code does not assume that this happens, so it's OK.

Even if I use placement new to explicitly change which union member is active, which ought to be a hint to the compiler that the active member changed, GCC still generates code that outputs 4321.

This looks like a bug with GCC and Clang assuming that the switching of active union member cannot happen here, because they fail to recognize the possibility of p1, p2 and p3 all pointing to the same object.

GCC and Clang (and pretty much every other compiler) support an extension to C/C++ where you can read an inactive member of a union (getting whatever potentially garbage value as a result), but only if you do this access in a member access expression involving the union. If v1 were not the active member, read_s1x would not be defined behavior under this implementation-specific rule, because the union is not within the member access expression. But because v1 is the active member, that shouldn't matter.

This is a complicated case, and I hope that my analysis is correct, as someone who isn't a compiler maintainer or a member of one of the committees.

这篇关于工会对这种使用是否严格遵守?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆