具有相同成员类型的C结构是否保证在内存中具有相同的布局?
基本上,如果我有
typedef struct { int x; int y; } A; typedef struct { int h; int k; } B;
我有A a
,C标准是否保证((B*)&a)->k
与ay
相同?
具有相同成员类型的C结构是否保证在内存中具有相同的布局?
几乎是的。 对我来说足够近了。
从n1516,第6.5.2.3节,第6段:
…如果一个union包含几个共享一个公共初始序列的结构……,并且如果union对象当前包含这些结构中的一个,则允许检查它们中的任何一个的公共初始部分。完成的联合类型是可见的。 如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列 。
这意味着如果您有以下代码:
struct a { int x; int y; }; struct b { int h; int k; }; union { struct aa; struct bb; } u;
如果您指定给ua
,标准表示您可以从ub
读取相应的值。 根据这一要求,它延伸了合理性的界限,表明struct a
和struct b
可以有不同的布局。 这样的系统将是病态的极端。
请记住,该标准还保证:
-
结构永远不会陷阱表示。
-
结构中字段的地址增加(
ax
总是在ay
之前)。 -
第一个字段的偏移量始终为零。
但是,这很重要!
你改写了这个问题,
C标准是否保证
((B*)&a)->k
与ay相同?
没有! 而且它非常明确地表明它们不一样!
struct a { int x; }; struct b { int x; }; int test(int value) { struct aa; ax = value; return ((struct b *) &a)->x; }
这是一种锯齿违规。
捎带其他回复,并发出关于第6.5.2.3节的警告。 显然,关于anywhere that a declaration of the completed type of the union is visible
的anywhere that a declaration of the completed type of the union is visible
的确切措辞存在争议,并且至少GCC没有按照书面forms实施 。 这里有一些切线的C WG缺陷报告, 此处还有委员会的后续评论。
最近我试图找出其他编译器(特别是GCC 4.8.2,ICC 14和clang 3.4)如何使用标准中的以下代码解释这一点:
// Undefined, result could (realistically) be either -1 or 1 struct t1 { int m; } s1; struct t2 { int m; } s2; 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; u.s1.m = -1; return f(&u.s1,&u.s2); }
GCC: -1, clang: -1, ICC: 1并警告有关别名违规
// Global union declaration, result should be 1 according to a literal reading of 6.5.2.3/6 struct t1 { int m; } s1; struct t2 { int m; } s2; union u { struct t1 s1; struct t2 s2; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union uu; u.s1.m = -1; return f(&u.s1,&u.s2); }
GCC: -1, clang: -1, ICC: 1但警告有关别名违规
// Global union definition, result should be 1 as well. struct t1 { int m; } s1; struct t2 { int m; } s2; union u { struct t1 s1; struct t2 s2; } u; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { u.s1.m = -1; return f(&u.s1,&u.s2); }
GCC: -1, clang: -1, ICC: 1,没有警告
当然,如果没有严格的别名优化,所有三个编译器每次都会返回预期的结果。 由于clang和gcc在任何情况下都没有明显的结果,唯一真实的信息来自ICC缺乏对最后一个的诊断。 这也与标准委员会在上述第一个缺陷报告中给出的例子一致。
换句话说,C的这个方面是一个真正的雷区,你必须要警惕你的编译器正在做正确的事情,即使你遵循标准的字母。 更糟糕的是,因为直观的这样一对结构应该在内存中兼容。
这种别名特别需要union
类型。 C11§6.5.2.3/ 6:
为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个, 则允许检查公共其中任何一个的初始部分都可以看到完整类型的联合声明。 如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列。
这个例子如下:
以下不是有效的片段(因为联合类型在函数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);} }
要求似乎是1.被别名的对象存储在一个union
并且该union
类型的定义在范围内。
对于它的价值,C ++中相应的初始子序列关系不需要union
。 通常,这种union
依赖对编译器来说是一种极端的病态行为。 如果某种方式存在联合类型可能会影响一个内存模型,那么最好不要试图想象它。
我想目的是内存访问validation程序(想想类固醇上的Valgrind)可以检查针对这些“严格”规则的潜在别名错误。