19

本质上,如果我有

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

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

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

4

3 回答 3

17

具有相同成员类型的 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;
}

这是一个混叠违规。

于 2013-11-06T06:03:00.273 回答
9

捎带其他回复,并带有关于第 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 语言的这个方面是一个真正的雷区,即使您严格遵守标准,您也必须警惕您的编译器正在做正确的事情。更糟糕的是,这样一对结构应该在内存中兼容是很直观的。

于 2013-11-06T08:40:05.330 回答
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. 被别名的对象存储在 aunion和 2. 该union类型的定义在范围内。

值得一提的是,C++ 中相应的初始-子序列关系不需要union. 一般来说,这种union依赖对于编译器来说是一种极其病态的行为。如果联合类型的存在会以某种方式影响具体的内存模型,那么最好不要尝试去描绘它。

我想目的是内存访问验证器(想想类固醇上的 Valgrind)可以根据这些“严格”规则检查潜在的别名错误。

于 2013-11-06T05:56:26.937 回答