具有相同成员类型的C结构是否保证在内存中具有相同的布局?

时间:2013-11-06 05:17:47

标签: c struct casting memory-layout

基本上,如果我有

typedef struct {
    int x;
    int y;
} A;

typedef struct {
    int h;
    int k;
} B;

我有A a,C标准是否保证((B*)&a)->ka.y相同?

3 个答案:

答案 0 :(得分:16)

  

具有相同成员类型的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 astruct b可以有不同的布局。这样的系统将是病态的极端。

请记住,该标准还保证:

  • 结构绝不是陷阱表示。

  • 结构中字段的地址增加(a.x始终在a.y之前)。

  • 第一个字段的偏移量始终为零。

然而,这很重要!

你改写了这个问题,

  

C标准是否保证((B*)&a)->k与a.y相同?

没有!它非常明确地说明它们不一样!

struct a { int x; };
struct b { int x; };
int test(int value)
{
    struct a a;
    a.x = value;
    return ((struct b *) &a)->x;
}

这是一种锯齿违规。

答案 1 :(得分:7)

对其他回复进行抄袭,并发出有关第6.5.2.3节的警告。显然,对anywhere that a declaration of the completed type of the union is visible的确切措辞存在争议,至少GCC doesn't implement it as written。有一些切线的C WG缺陷报告herehere以及委员会的后续评论。

最近,我试图找出其他编译器(特别是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的这个方面是一个真正的雷区,你必须警惕你的编译器正在做正确的事情,即使你遵循标准的字母。更糟糕的是,因为它很直观,这样的结构应该在内存中兼容。

答案 2 :(得分:3)

这种别​​名特别需要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和2. union类型的定义在范围内。

对于它的价值,C ++中相应的初始子序列关系不需要union。一般来说,这种union依赖性对编译器来说是一种极端的病态行为。如果某种方式存在一个联合类型可能会影响一个内存模型,那么最好不要试图想象它。

我认为意图是内存访问验证程序(想想类固醇上的Valgrind)可以针对这些“严格”规则检查潜在的别名错误。