2013-12-23 30 views
4

我不确定代码是否在asserts强制转换中存在指针别名(或其他标准一致性问题)。看来,指向联合类型的指针应该能够转换为第一个成员的指针,并且因为联合只由这两个结构组成,所以我认为对第一个成员的转换应该可行,但我不是确定如果这是正确的,或者如果我在这个过程中填充细节的细节。工会是否需要填充高位?与普通第一成员结合的结构体

看起来这是不明确的行为?有没有人有任何洞察力,这是否是支持的。我知道有利用结构与enum type场和struct container_storage成员这样做的另一种标准的方式,但它似乎是浪费空间考虑,这个信息已经在struct contained

编译命令在Linux中:gcc -std=c99 -Wextra -pedantic -fstrict-aliasing test.c && ./a.out && echo $?返回0

#include <stdlib.h> 
#include <assert.h> 

enum type {type_a = 1, type_b = 2}; 

struct contained { 
    int some_other_field; 
    enum type type; 
}; 

struct container_a { 
    struct contained contained; 
    int test; 
}; 


struct container_b { 
    struct contained contained; 
    char test; 
}; 

union container_storage { 
    struct container_a container_a; 
    struct container_b container_b; 
}; 

int 
main(int argc, char **argv) 
{ 
    union container_storage a = 
     {.container_a = {.contained = {.type = type_a}, .test = 42}}; 
    union container_storage b = 
     {.container_b = {.contained = {.type = type_b}, .test = 'b'}}; 

    assert(((struct contained *)&a)->type == type_a); 
    assert(((struct contained *)&b)->type == type_b); 

    return EXIT_SUCCESS; 
} 

参考文献:

[1] gcc, strict-aliasing, and casting through a union

[2] What is the strict aliasing rule?

回答

2

这应该没问题。 C11,6.5.2.3/6(“结构和工会会员”)说:如果一个联合包含 共享一个公共初始序列几种结构:

一个特殊的保障是为了简化使用工会制成(见下文),并且如果工会对象当前包含这些结构中的一个,则允许检查其中任何一个的公共 的初始部分,其中可以看到工会 的完成类型的声明。如果对应的成员 对于一个或多个初始成员的序列具有兼容的类型(并且对于位域,具有相同的宽度),则两个结构共享共同的初始序列

(C++使得同样的保证(C++ 11,9.2/18),用于标准布局联合。)

+0

很高兴听到。我试图挖掘C99规范,但我似乎无法在任何地方找到它。谢谢! – backscattered

2

union不要垫,他们只是覆盖他们的成员。任何struct的第一个成员保证立即开始,没有填充。一般而言,struct以相同类型的相同成员开头,保证与该初始部分具有相同的布局。

1

在C89,结构类型的指针识别一个联合的成员可以是用于检查作为与存储在其中的数据类型共享的共同初始序列的一部分的任何成员。这反过来一般意味着可以使用指向任何结构类型的指针来检查与任何其他类型共享的公共初始序列的任何成员(如果该对象碰巧是声明的联合对象的成员,则这种行为将被明确定义,编译器在这些情况下产生所需行为的唯一可行方法是为所有人维护它)。

C99增加了一个额外的要求,即CIS保证仅适用于包含两种结构的联合类型可见的情况,一些编译器编写者似乎认为它仅适用于通过联合类型直接执行的访问。这样的编译器的作者似乎认为将需要一个共同的头部像处理功能的功能:

struct smallThing { void *next; uint16_t length; uint8_t dat[2]; }; 
struct bigThing { void *next; uint16_t length; uint8_t dat[65528]; }; 

应该是把解压出来的标题,如:

struct uHeader { void *next; uint16_t length; }; 
struct smallThing { uHeader head; uint8_t dat[2]; }; 
struct bigThing { uHeader head; uint8_t dat[15994]; }; 

或使用联合型尽管使用uHeader将会使struct smallThing的大小增加50%(并且完全破坏任何代码, 依赖于它的布局),并且在大多数对象只需要很小的情况下对所有东西使用联合会增加内存使用千倍。

如果需要代码与基本上忽略公共初始序列规则的编译器兼容,则应该将公共初始序列规则视为基本无用。就我个人而言,我认为最好记录一下,只有那些尊重CIS的编译器应该被认为适合与代码一起使用,而不是为了适应不适合的编译器而向后退避开,但我认为重要的是要知道像后者这样的编译器有一些存在。

据我所知,clang和gcc不以任何有用的方式兑现CIS规则,除非设置了-fno-strict-aliasing标志。我不知道其他编译器。

+0

这是一个很好的答案。我认为编写一个不合规的编译器列表将是一个好主意。 – backscattered

+1

@backscattered:我不知道有多少编译器在决定强制遵守Common Initial Sequence保证的唯一方法时应该禁用所有基于类型的优化,但恕我直言,保证的目的是明确,坚持它的成本应远远低于对所有指针访问进行悲观推定的成本。 – supercat