关于C11标准中工会示例的澄清

关于C11标准中工会示例的澄清,c,unions,c11,C,Unions,C11,C11标准6.5.2.3中给出了以下示例 以下不是有效的片段(因为联合类型不是 在函数f中可见): struct t1{int m;}; 结构t2{int m;}; int f(结构t1*p1,结构t2*p2) { 如果(p1->mm=-p2->m; 返回p1->m; } int g() { 联合{ 结构t1-s1; 结构t2s2; }u; /* ... */ 返回f(&u.s1和&u.s2); } 为什么union类型对函数f可见很重要 在多次阅读相关章节的过程中,我看不到包含章节中有任何内

C11标准6.5.2.3中给出了以下示例

以下不是有效的片段(因为联合类型不是 在函数f中可见):

struct t1{int m;};
结构t2{int m;};
int f(结构t1*p1,结构t2*p2)
{
如果(p1->m<0)
p2->m=-p2->m;
返回p1->m;
}
int g()
{
联合{
结构t1-s1;
结构t2s2;
}u;
/* ... */
返回f(&u.s1和&u.s2);
}
为什么union类型对函数f可见很重要


在多次阅读相关章节的过程中,我看不到包含章节中有任何内容不允许这样做。

这很重要,因为6.5.2.3第6段(添加了强调):

为简化工会的使用,提供了一项特殊保证: 如果一个并集包含多个共享公共首字母的结构 序列(请参见下文),以及union对象当前是否包含一个 在这些结构中,允许检查公共初始值 其中任何一个的一部分,该部分包含已完成类型的声明 联盟的成员可见。两个结构共享一个共同的首字母 如果相应的成员具有兼容的类型(和,对于 一个或多个初始值序列的位字段(相同宽度) 成员们

这不是一个需要诊断的错误(语法错误或约束冲突),但行为未定义,因为
struct t1
struct t2
对象的
m
成员占用相同的存储空间,但是由于
struct t1
struct t2
是不同的类型,编译器可以假设它们不是不同的类型——特别是对
p1->m
的更改不会影响
p2->m
的值。例如,编译器可以在第一次访问时将
p1->m
的值保存在寄存器中,然后在第二次访问时不从内存重新加载它。

注意:这个答案并不能直接回答您的问题,但我认为它是相关的,并且太大,无法在注释中显示


我认为代码中的示例实际上是正确的。联合公共初始序列规则确实不适用;但也没有任何其他规则会使该代码不正确

通用初始顺序规则的目的是确保结构的布局相同。然而,这在这里甚至不是问题,因为结构只包含一个
int
,并且结构不允许有初始填充

注意,如前所述,ISO/IEC文件中标题为注释或示例的章节为“非规范性”章节,这意味着它们实际上不构成规范的一部分


有人认为此代码违反了严格的别名规则。以下是C116.5/7中的规则:

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型, [……]
在本例中,正在访问的对象(由
p2->m
p1->m
表示)的类型为
int
。左值表达式
p1->m
p2->m
的类型为
int
。由于
int
int
兼容,因此不存在冲突

确实,
p2->m
表示
(*p2).m
,但是此表达式不访问
*p2
。它只访问
m


以下任一项都是未定义的:

*p1 = *(struct t1 *)p2;   // strict aliasing: struct t2 not compatible with struct t1
p2->m = p1->m++;          // object modified twice without sequence point

鉴于声明:

union U { int x; } u,*up = &u;
struct S { int x; } s,*sp = &s;
左值
u.x
up->x
s.x
sp->x
都是
int
类型,但对这些左值中任何一个的访问(至少在指针初始化如图所示的情况下)也将访问
union u
struct s
类型的对象的存储值。由于N1570 6.5p7仅允许通过类型为字符类型的左值或包含类型为
union U
struct S
的对象的其他结构或联合访问这些类型的对象,因此它不会对尝试使用任何这些左值的代码的行为强加任何要求

我认为很明显,该标准的作者希望编译器至少在某些情况下允许使用成员类型的左值访问结构或联合类型的对象,但不一定允许成员类型的任意左值访问结构或联合类型的对象。没有任何规范性规定来区分应允许或不允许此类访问的情况,但有一个脚注表明,该规则的目的是指明何时可以或何时不可以访问

如果将该规则解释为仅适用于左值的使用方式与其他类型看似不相关的左值相似的情况,则该解释将定义代码的行为,如:

struct s1 {int x; float y;};
struct s2 {int x; double y;};
union s1s2 { struct s1 v1; struct s2 v2; };

int get_x(void *p) { return ((struct s1*)p)->x; }
当后者被传递时,
struct s1*
struct s2*
union s1s2*
标识其类型的对象,或
union s1s2
的任一成员的新派生地址。在任何上下文中,如果实现能够看到足够多的信息,从而有理由关心原始左值和派生左值上的操作是否会相互影响,那么它就能够看到它们之间的关系


但是,请注意,这样的实现不需要考虑代码中出现别名的可能性,如下所示:

struct position {double px,py,pz;};
struct velocity {double vx,vy,vz;};

void update_vectors(struct position *pos, struct velocity *vel, int n)
{
  for (int i=0; i<n; i++)
  {
    pos[i].px += vel[i].vx;
    pos[i].py += vel[i].vy;
    pos[i].pz += vel[i].vz;
  }
}
结构位置{double px,py,pz;}; 结构速度{double vx,vy,vz;}; 无效更新向量(结构位置*位置,结构速度*水平,整数n) {
for(int i=0;它的并集是定义它的块的局部的。一个并集可以通过在所有函数之外定义它而成为全局的,但在它第一次被使用之前。@ArifBurhan:我想OP知道
struct position {double px,py,pz;};
struct velocity {double vx,vy,vz;};

void update_vectors(struct position *pos, struct velocity *vel, int n)
{
  for (int i=0; i<n; i++)
  {
    pos[i].px += vel[i].vx;
    pos[i].py += vel[i].vy;
    pos[i].pz += vel[i].vz;
  }
}