转换,结构和共同的初始序列

时间:2019-08-08 11:07:26

标签: c pointers casting language-lawyer undefined-behavior

给出:

struct A {
    int i;
    char c;
    double d;

    int x;
    char y;
    double z;
};

struct B {
    int i;
    char c;
    double d;

    int x;
    long y;
    double z;
};

struct A *a = ...

据我所知,这是可以的,因为要访问的字段是两个结构的公共初始序列的一部分:

((struct B *)a)->d = 1.2;

但这不是,因为不同的y字段使z不属于公共初始序列的一部分:

((struct B *)a)->z = 1.2;

这正确吗?

3 个答案:

答案 0 :(得分:3)

根据标准6.5.2.3 p5:

  

为了简化联合的使用,做出了一项特殊保证:   如果一个联合包含几个共享共同的缩写的结构   序列(请参见下文),以及联合对象当前是否包含一个   在这些结构中,可以检查共同的姓名缩写   其中任何一个部分的完整类型的声明   联合可见。两个结构共享一个共同的初始序列   如果相应的成员具有兼容的类型(对于位域,   相同的宽度)以表示一个或多个初始成员的序列。

所以您的第一个代码片段几乎是 。如果在那时可以看到涉及它们的联合,则C标准仅允许您使用公共初始序列。因此,在union Foo{ struct A; struct B; };A的声明之后添加B可以使您的第一个代码段定义明确,即使您正在检查的结构实际上没有存储在这样的结构中工会。 (大概这种严格意义很重要,因为它允许编译器对您不打算使用别名的结构进行更大胆的优化。)

正如th33lf所说,第二段是UB,句段。

答案 1 :(得分:2)

据我了解,按照严格的别名规则,当前在所有平台上都是未定义的行为。如果将两个结构都包装在同一个联合中,则第一个示例可能会按预期工作。但是,您的第二个示例变得依赖于实现-它可能在强制执行等于或大于long大小的对齐方式的平台上工作,而在其他平台上则不行。

union C 
{
    struct A a;
    struct B b;
};

C *c;
c->a.d = 1.2; // Valid
c->b.d = 1.5; // Valid and sets the value of a.d as well
c->b.z = 1.2; // Valid but may not set the value of a.z as expected

答案 2 :(得分:2)

N1570 6.5p7的编写方式不允许使用非字符成员类型的左值访问结构或联合的任何操作(它确实允许使用任意左值访问结构或联合的成员包含封闭结构或联合类型,但不能相反)。因此,给定类似以下内容:

struct blob { int size; int *dat; };

void clear_blob(blob *p)
{
  for (int i=0; i < p->size; i++)
    p->dat[i] = 0;
}

该标准避免了(恕我直言,尽管我并非委员会的所有成员都故意注意到这一点)要求编译器考虑到p->dat可能拥有p->size的地址,而{ {1}}的值等于或大于2。在这种情况下,存储将导致p->size保持为零,这又将导致循环在其第一次迭代后退出,而不是运行size迭代。

当然,如果至少在某些情况下,如果不能依赖实现以允许使用成员类型的左值访问结构和联合的存储值,则该语言将在很大程度上无用,但是委员会保留了确切的问题。在标准的管辖范围之外,那些情况应该是实现质量问题,希望人们为各种客户(数字管理员,系统程序员等)设计编译器的人不外乎委员会就其特定客户这样,并且不需要强制要求编译器支持编译器编写者必须故意钝化而忽略的结构。

除非使用size模式,否则clang和gcc都不会可靠地支持采用联合成员地址并使用指针访问该类型的对象的代码。甚至给出类似的内容:

-fno-strict-aliasing

他们不会意识到对struct s1 {int x;}; struct s2 {int x;}; union { struct s1 v1[10]; struct s2 v2[10];} uarr; int test(int i, int j) { if ((uarr.v1+i)->x) (uarr.v2+j)->x = 0; return (uarr.v1+i)->x; } 的访问可能会与(uarr.v2+j)->x进行交互。但是,处理代码的能力并不是一致性所必需的,而仅仅是实现质量的问题。尽管当前版本的clang和gcc恰好使用(uarr.v1+i)->x运算符支持类似的代码,但在不等同于[]运算符的任何情况下,该标准均未强制其行为E1[E2]-clang和gcc不支持的形式。