Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7303839
  • 博文数量: 159
  • 博客积分: 10424
  • 博客等级: 少将
  • 技术积分: 14605
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-14 12:45
个人简介

啦啦啦~~~

文章分类
文章存档

2015年(5)

2014年(1)

2013年(5)

2012年(10)

2011年(116)

2010年(22)

分类: C/C++

2012-01-17 22:24:45

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
今天在做代码的enhancement,其中涉及到修改一个结构体,类似于下面的代码:
  1. struct my_type {
  2.    unsigned int flag;
  3. #define MY_FLAG_1 0x1
  4. #define MY_FLAG_2 0x2
  5. #define MY_FLAG_3 0x4
  6. };
这里struct my_type->flag作为标志位,通过宏来定义不同的标志。其实我觉得这样的代码是可以接受的。但是我们的工程代码中,更多的是应用位域来表示标志位。为了风格的统一,我决定修改这个结构体,改为:
  1. struct my_type {
  2.    unsigned int flag1:1,
  3.                 flag2:1,
  4.                 flag3:1,
  5.                 spare:29;
  6. };
然后再修改所有使用原标志位的地方。位域确实会比使用标志位更清晰,但是会有如下的老代码:
  1. struct my_type t1, t2;

  2. t1.flag = t2.flag
原来仅仅是对一个变量struct my_type->flag进行赋值。现在使用位域,代码就要写为
  1. struct my_type t1, t2;

  2. t1.flag1 = t2.flag1;
  3. t2.flag2 = t2.flag2;
  4. t3.flag3 = t2.flag3;
比以前使用一个flag变量要麻烦冗余的多——这里不适用memcpy,只是为了复制标志位。这样的代码还有不少地方。改为位域,代码的清晰度和可读性是比以前好了,但是这样的代码看上去比以前麻烦的多。
于是我很自然的使用了union来避免这个问题:参加我这篇关于使用union来重构的文章

新的结构体定义如下:
  1. struct my_type {
  2.    union {
  3.        unsigned int flag1:1,
  4.                     flag2:1,
  5.                     flag3:1,
  6.                     spare:33;
  7.        unsigned int all_flags;
  8.    };
  9. };


  10. struct my_type t1, t2;

  11. t1.all_flags = t2.all_flags;
我的意图很明显,通过使用union,在不增加存储空间的条件下,既可以直接使用flag1,flag2和flag3,而当复制标志位时,通过共享的存储空间,可以直接复制all_flags来复制所有的标志位。

Ok。开始测试自己的修改,发现程序运行有问题——聪明的朋友应该已经知道问题是什么了。
下面是一个示例程序:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>

  4. struct my_type {
  5.    union {
  6.        unsigned int flag1:1,
  7.                     flag2:1,
  8.                     flag3:1,
  9.                     spare:29;
  10.        unsigned int all_flags;
  11.    };
  12. };

  13. int main(void)
  14. {
  15.     struct my_type t;

  16.     memset(&t, 0, sizeof(t));
  17.     t.flag1 = 1;

  18.     printf("%d %d %d\n", t.flag1, t.flag2, t.flag3);

  19.     return 0;

  20. }
编译输出:
  1. [fgao@fgao-vm-fc13 test]$ ./a.out
  2. 1 1 1

看到程序的输出,我就知道自己想的过于草率了。当时认为flag1,flag2,flag3及spare是属于一个unsigned int的不同存储位置,可以和后面的my_flags共享一个unsigned int的存储空间。这里绝对的想岔了。

还是想引用一下C99标准关于union的定义:
—A union type describes an overlapping nonempty set of member objects, each of
which has an optionally specified name and possibly distinct type.

通过union的定义,可以清楚的看到,union针对的是member objects。只要是union的成员,都是共享一个重叠的存储空间。那么对于上面的例子来说,flag1,flag2,flag3和spare都是从union的一个unsigned int的最低位开始存储——小端从最低位开始,大端从最高位开始。也就是说flag1,flag2和flag3共享了最低位。

问题是搞清楚了,但是对自己却是一个提醒。如果说给我写出这些代码,让我review的话,我可能会立刻看出问题。但是这些代码是我自己写出的时候,却会写出这样的代码。看来,还是对union的概念不够清楚。

最后,如果想改正这个问题的话,也比较简单。
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>

  4. struct my_type {
  5.    union {
  6.        struct {
  7.            unsigned int flag1:1,
  8.                         flag2:1,
  9.                         flag3:1,
  10.                         spare:29;
  11.        };
  12.        unsigned int all_flags;
  13.    };
  14. };

  15. int main(void)
  16. {
  17.     struct my_type t;

  18.     memset(&t, 0, sizeof(t));
  19.     t.flag1 = 1;

  20.     printf("%d %d %d 0x%X\n", t.flag1, t.flag2, t.flag3, t.all_flags);

  21.     return 0;

  22. }
再用一个独立的匿名struct封装flag1,flag2,flag3和spare。这样对于union来说,将把这一struct视为它的成员变量,就不会出现上面的问题了。但是这样的定义,看上去就有些奇怪了。是否选择这种方法,就是仁者见仁智者见智了。我是没有使用这种方法,而是老老实实地的复制flag1,flag2和flag3。


注:这里的代码union和struct是应用了GNU的扩展特性匿名的union和struct。

阅读(7520) | 评论(3) | 转发(4) |
给主人留下些什么吧!~~

GFree_Wind2014-03-22 10:15:09

seufy88:以下是一位CUer的贴子

位域的不可移植,其实是指不同的编译器,它可以选择不同的实现方式。

比如有的编译器可以从最高位开始排列,有的编译器可能从最低位开始排列。

struct union
{
        struct
        {
                u32 bit0 : 1;
                u32 bit1 : 1;
            &nbs

这个是跟cpu相关,跟大小端有关系。

回复 | 举报

seufy882014-03-18 20:31:25

以下是一位CUer的贴子

位域的不可移植,其实是指不同的编译器,它可以选择不同的实现方式。

比如有的编译器可以从最高位开始排列,有的编译器可能从最低位开始排列。

struct union
{
        struct
        {
                u32 bit0 : 1;
                u32 bit1 : 1;
            &nbs

seufy882014-03-18 20:27:58

我想问一下问题,
如果使用all_flag的方式去一次性赋值的话,是否要考虑bit位的大小端的问题



all_falg = 10001001
中,bit 0不一定是赋值给了 flag1:1,而有可能是高位的bit 7赋值给了flag1:1 ?