以下示例在C11标准6.5.2.3
中给出以下不是有效的片段(因为联合类型不是 在函数f)中可见:
struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union { struct t1 s1; struct t2 s2; } u; /* ... */ return f(&u.s1, &u.s2); }
为什么联合类型对函数f可见是否重要?
在阅读相关部分几次后,我在包含部分中看不到任何禁止此内容的内容。
答案 0 :(得分:13)
重要的是因为6.5.2.3第6段(强调增加):
为简化工会的使用,我们做出了一项特殊保证: 如果联合包含多个共享公共首字母的结构 序列(见下文),如果union对象当前包含一个 在这些结构中,允许检查共同的初始 其中任何一部分完成类型声明的任何地方 联盟的可见性。两个结构共享一个公共首字母 序列如果相应的成员具有兼容的类型(并且,对于 比特字段,相同的宽度)用于一个或多个初始序列 成员。
这不是需要诊断(语法错误或约束违规)的错误,但行为未定义,因为m
和struct t1
对象的struct t2
成员占用相同的行为存储,但因为struct t1
和struct t2
是不同的类型,允许编译器假定它们没有 - 特别是对p1->m
的更改不会影响{{1}的值}}。例如,编译器可以在第一次访问时将p2->m
的值保存在寄存器中,然后在第二次访问时不从内存中重新加载它。
答案 1 :(得分:1)
注意:这个答案并没有直接回答你的问题,但我认为它是相关的,而且太大了,无法发表评论。
我认为代码中的示例实际上是正确的。联合公共初始序列规则并不适用,这是正确的。但也没有任何其他规则会使这段代码不正确。
公共初始序列规则的目的是保证结构的相同布局。但是这里甚至不是问题,因为结构只包含一个int
,并且结构不允许有初始填充。
请注意,正如所讨论的here,标题为 Note 或示例的ISO / IEC文档中的部分是非规范的&#34;这意味着它们实际上并不构成规范的一部分。
有人建议此代码违反严格的别名规则。这是规则,从C11 6.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
答案 2 :(得分:1)
给出声明:
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;
}
}
即使“公共初始序列”保证似乎允许这样做。
这两个示例之间有很多区别,因此有许多迹象表明编译器可以用来允许将第一个代码的现实可能性传递给struct s2*
,它可能会访问struct s2
,而不必考虑第二次检查中对pos[]
的操作可能会影响vel[]
的元素的可能性。
即使没有声明任何union
类型,许多寻求以有用的方式有效支持Common Initial Sequence规则的实现也能够处理第一个,而且我不知道该标准的作者打算这样做。仅添加一个union
类型声明应强制编译器考虑其中成员的常见初始序列之间任意混叠的可能性。我看到的提及联合类型的最自然的意图是,无法感知第一个示例中存在的众多线索中的任何线索的编译器可以使用是否存在以两种类型为特征的完整联合类型声明来指示是否存在联合类型一种此类的左值可用于访问另一种。
请注意,N1570 P6.5p7及其之前的产品都没有做出任何努力来描述当给定的使用聚合的代码时,质量实现应可预测地表现的所有情况。大多数此类情况留待实施质量问题解决。由于低质量但合规的实现几乎出于其认为合适的任何原因而被允许以荒谬的方式行事,因此没有必要将标准复杂化,因为有人尽全力编写高质量的实现会处理是否这是一致性的要求。