使用UNION和STRUCTURE

时间:2014-06-22 06:54:34

标签: c gcc struct unions

这种做法是否正确?

struct netinfo
{
   // a lot of members here...
   union
   {
     UINT type;
     struct
     {
        UINT   _type;    // a place holder (obviously who access this struct already knows the type is ipv4)
        UINT32 saddr;
        UINT32 daddr;
        UINT   ttl.
     } ip4;
     struct
     {
       UINT  _type;        // a place holder (obviously who access this struct already knows the type is ipv6)
       UINT8 saddr[16];
       UINT8 daddr[16];
     } ip6;
  }  protocol;
  // a lot of members here...
};

struct netinfo my_netinfo;

my_netinfo.protocol.type = NETTYPE__IPV4; // note that i used the "protocol" union as the "type"  member
my_netinfo.protocol.ip4.saddr = addr1;     // now i'm accessing the union as the  "ip4" member
my_netinfo.protocol.ip4.daddr = addr2;
my_netinfo.protocol.ip4.ttl = 64;

// ...

// later, i pretend to acess protocol.type and protocol.ip4.X, the way i want

请注意,我的目的是在同一时间访问它:

my_netinfo.protocol.type和 my_netinfo.protocol.ip4

我不会使用my_netinfo.protocol.ip4.type,因为通过访问my_netinfo.protocol.ip4,我已经知道我正在处理IPv4。

问题是:

通过在结构中使用_type作为其第一个成员,这种方法是否有效并且是一个好主意?

union { int x; struct { int _padding_for_x; int a; int b; } y };

这样就更换了union_namex,union_name.y.a,union_namey.y.b,union_name.x ......

我能以任何顺序再次写/读/写/读取所有这些吗?

3 个答案:

答案 0 :(得分:2)

除了下面关于删除冗余UINT type的问题的评论之外,对ip4和ip6使用两个不同的结构可能更有意义,而不是试图使它们成为netinfo联合的成员。这就是原因。虽然联合不要求成员具有相同的大小,但联合只会使用足够大的内存来容纳最大的成员大小。请参阅:glibc reference manual这是由于联合一次只保存一组数据。根据你的问题,这可能行不通:

Note that my intention is access it as, AT THE SAME TIME:

你可以同时访问netinfo.ip4和netinfo.ip6 同时,但除非你的代码在ip4和ip6上都能正常运行,不管用什么用来填充联盟之前的联盟读,你的代码将无法正常工作。回想一下,您可以通过union中包含的两种数据类型访问相同的数据,但如果两种类型的数据不相同,则无法保证您访问的内容对两种类型都有意义。这导致了你的最终问题:

Can I write/read/write/read again all of them in any order?

你可以随时写入联盟,但是你只会同时获得两种类型的有用数据当且仅当时,联合中存储的数据才有效ip4和ip6同时进行。鉴于ip4和ip6成员的存储差异不太可能。

因此,您可以存储ip4并阅读ip4,您可以存储ip6并阅读ip6,但您无法存储ip4.saddr然后预期无问题地阅读ip6.saddr。联盟之外的单独结构将不那么成问题。

答案 1 :(得分:0)

答案是否定的。

该标准要求联盟中只有一名成员一次处于活动状态。有一项豁免允许您检查common initial sequenceUINT _type),但其他成员不能同时处于活动状态。执行此操作的程序是ill-formed(与Undefined Behaviour一样糟糕。)

它不仅理论上不好,而且实际上也很糟糕。对齐可能性使得几乎不可能预测该联合的布局。你的数据存在于某个地方,但只有编译器及其制造商知道在哪里。

所以不,这也不是一个好主意。

答案 2 :(得分:0)

简短回答

就像我在问题的评论中提出的那样:使用包含单独结构标记的联合。使用union,您可以更好地利用空间,使用单独的结构标记将避免指针别名。这在理论上是合理的,实际上是一种常见的做法。

更长的答案

由于您提供了用C编写的网络代码,因此我假设您的目标是提高性能。因此,您不希望在复制缓冲区上浪费额外的空间和时间。这是使用union的动机,它只与它包含的最大类型一样大,并允许您将内存位置视为总和类型(ipv4或ipv6)。在优化低级数据混洗时,您应该注意一些陷阱。

当您从不同的指针类型访问相同的内存位置时,会发生

指针别名。在您的情况下,您可以从指向IPv4消息的指针或指向IPv6消息的指针访问网络消息缓冲区。 这很顽皮,因为当编译器优化和多线程发挥作用时,会降低性能和/或非常奇怪的错误。

我建议您阅读this SO community wiki以获得严格别名规则的详细说明。它还警告你应该考虑的其他事情,比如endianess,alignment and packing。

继续前进。幸运的是,指向具有不同标记的联合类型的指针不是别名。这就是使用具有不同标签的联合的原因。话虽如此,尝试从包含IPv6的内存位置读取IPv4数据仍然是一个愚蠢的想法,编译器不会帮助您避免。所以不要这样做。

此代码应该有效(完全披露 - 我没有尝试编译它):

struct netinfo
{
   // a lot of members here...
   struct
   {
     UINT type;
     union 
     {
         struct
         {
            UINT32 saddr;
            UINT32 daddr;
            UINT   ttl.
         } ip4;
         struct
         {
           UINT8 saddr[16];
           UINT8 daddr[16];
         } ip6;
     } data;
  }  protocol;
  // a lot of members here...
};

您可能会觉得有用的其他链接

http://en.wikipedia.org/wiki/Pointer_aliasing http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html