这种工会的使用是否严格符合?

sup*_*cat 18 c gcc clang strict-aliasing

鉴于代码:

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));
}
Run Code Online (Sandbox Code Playgroud)

整个程序中存在一个联合对象 - q.它的活动成员设置为v1,然后是v2,然后v1再次设置.代码仅q.v1在该成员处于活动状态时使用address-of运算符或结果指针,同样如此q.v2.由于p1,p2p3都是相同类型,因此p3->v1用于访问p1->v1p3->v2访问应该是完全合法的p2->v2.

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

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

Myr*_*ria 10

我相信你的代码是符合的,并且-fstrict-aliasingGCC和Clang模式存在缺陷.

我找不到C标准的正确部分,但是在我为C++模式编译代码时会出现同样的问题,我确实找到了C++标准的相关段落.

在C++标准中,[class.union]/5定义了=在union访问表达式上使用operator时会发生什么.C++标准规定当联合体涉及内置运算符的成员访问表达式时,=联合的活动成员将更改为表达式中涉及的成员(如果类型具有一个简单的构造函数,但是因为这是C代码,它确实有一个简单的构造函数).

请注意,write_s2x 无法更改联合的活动成员,因为联合不参与赋值表达式.您的代码并不认为会发生这种情况,所以没关系.

即使我使用placement new来显式更改哪个union成员处于活动状态,这应该是对活动成员更改的编译器的提示,GCC仍会生成输出的代码4321.

这看起来像GCC和Clang的错误,假设活动联合成员的切换不能在这里发生,因为他们无法识别可能性p1,p2并且p3都指向同一个对象.

GCC和Clang(以及几乎所有其他编译器)都支持C/C++扩展,您可以在其中读取union的非活动成员(获取任何可能的垃圾值),但前提是您在成员访问中执行此访问涉及工会的表达. 如果 v1不是活动成员,则read_s1x不会在此特定于实现的规则下定义行为,因为联合不在成员访问表达式中.但因为v1是活跃的成员,这应该不重要.

这是一个复杂的案例,我希望我的分析是正确的,不是编译器维护者或其中一个委员会的成员.

  • @ PeterJ_01:从我所看到的,大多数这样的例子涉及的代码可能会使用对标准的充分扭曲的解释,被视为调用UB.我的目标是有一个例子,其行为明确,明确,并且无可否认地由标准定义. (2认同)
  • @ PeterJ_01:这不仅仅是gcc和clang.godbolt上的许多编译器都表现出相同的行为,这表明行为是设计的,这让我想知道编译器编写者是否正在以证明其行为的方式解释标准. (2认同)