本质上,如果我有
typedef struct {
int x;
int y;
} A;
typedef struct {
int h;
int k;
} B;
我有A a
,C 标准是否保证((B*)&a)->k
与 相同a.y
?
本质上,如果我有
typedef struct {
int x;
int y;
} A;
typedef struct {
int h;
int k;
} B;
我有A a
,C 标准是否保证((B*)&a)->k
与 相同a.y
?
具有相同成员类型的 C 结构是否保证在内存中具有相同的布局?
几乎是的。对我来说足够近了。
从 n1516,第 6.5.2.3 节,第 6 段:
...如果联合包含多个共享公共初始序列的结构...,并且如果联合对象当前包含这些结构之一,则允许在声明已完成的联合类型可见。如果对应的成员对于一个或多个初始成员的序列具有兼容的类型(并且对于位域,具有相同的宽度),则两个结构共享一个共同的初始序列。
这意味着如果您有以下代码:
struct a {
int x;
int y;
};
struct b {
int h;
int k;
};
union {
struct a a;
struct b b;
} u;
如果您分配给u.a
,标准说您可以从中读取相应的值u.b
。考虑到这个要求,它扩展了合理性的范围来建议struct a
并且struct b
可以有不同的布局。这样的系统将是极端病态的。
请记住,该标准还保证:
结构永远不是陷阱表示。
结构中的字段地址增加(a.x
总是在之前a.y
)。
第一个字段的偏移量始终为零。
你改写了这个问题,
C标准是否保证
((B*)&a)->k
与ay相同?
不!它非常明确地指出它们是不一样的!
struct a { int x; };
struct b { int x; };
int test(int value)
{
struct a a;
a.x = value;
return ((struct b *) &a)->x;
}
这是一个混叠违规。
捎带其他回复,并带有关于第 6.5.2.3 节的警告。显然anywhere that a declaration of the completed type of the union is visible
,关于. 这里和这里有一些切向的 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 u u;
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. 被别名的对象存储在 aunion
和 2. 该union
类型的定义在范围内。
值得一提的是,C++ 中相应的初始-子序列关系不需要union
. 一般来说,这种union
依赖对于编译器来说是一种极其病态的行为。如果联合类型的存在会以某种方式影响具体的内存模型,那么最好不要尝试去描绘它。
我想目的是内存访问验证器(想想类固醇上的 Valgrind)可以根据这些“严格”规则检查潜在的别名错误。