这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » 结构体中的位域

共7条 1/1 1 跳转至

结构体中的位域

专家
2014-04-21 09:49:32     打赏

结构体和结构体位域在linux驱动设计中需要格外注意,为节约空间,通常会进行位域操作。对于结构体对齐不熟悉的同学,接着往下看。

 

有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下前人的经验(在没有#pragma pack宏的情况下):

      
原则1数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset0的地方以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int32位机为4字节,则要从4的整数倍地址开始存储)。

      
原则2结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct bb里有charintdouble等元素,那b应该从8的整数倍开始存储。)

      
原则3收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐

      
这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

      
1struct {
                     short a1; 2
                     short a2; 2
                     short a3; 2
                    }A;      6

struct{
                   long a1; 4
                   short a2; 2
                  }B;        8

       sizeof(A) = 6;
这个很好理解,三个short都为2

       sizeof(B) = 8;
这个比是不是比预想的大2个字节?long4short2,整个为8,因为原则3

      
2struct A{
                    int a; 4
                    char b; 1
                    short c; 2
                    };         8

struct B{
                   char b; 1
                   int a; 4
                   short c; 2
                    };

       sizeof(A) = 8; int
4char1short2,这里用到了原则1和原则3

       sizeof(B) = 12;
是否超出预想范围?char1int4short2,怎么会是12?还是原则1和原则3

      
深究一下,为什么是这样,我们可以看看内存里的布局情况。

                     int a   char b      short  c
       A
的内存布局:1111,     1*,       11

                     char b    int a     short c
       B
的内存布局:1***,     1111,   11**

      
其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为cshort,其起始位置要为2的倍数,就是原则1c的后面没有补充,因为bc正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。

       B
中,bchar1b后面补充了3个字节,因为aint4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

      
再看一个结构中含有结构成员的例子:

      
3struct A{
                     int a; 4
                     double b; 8
                     float c; 4
                    };

                struct B{
                     char e[2];
                     int f;
                     double g; 
                     short h;
                     struct A i;
                    };

       sizeof(A) = 24;
这个比较好理解,int4double8float4,总长为8的倍数,补齐,所以整个A24

       sizeof(B) = 48;
看看B的内存布局。

                                 e         f             g                h                                    i
       B
的内存布局:11* *,   1111,   11111111, 11 * * * * * *,        1111* * * *, 11111111, 1111 * * * *

       i
其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。

      
以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;

      
有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗。

                                  a                b             c
       A
的内存布局:1111,     11111111,   1111

                                 e        f             g          h                     i
       B
的内存布局:11,   1111,   11111111, 11 ,            1111, 11111111, 1111

      
#pragma pack(2)的结果又是多少呢?#pragma pack(4)呢?留给大家自己思考吧,相信没有问题。

      
还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取sizeof值。C99规定intunsigned intbool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

      
使用位域的主要目的是压缩存储,其大致规则为:
       1)
如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
       2)
如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
       3)
如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
       4)
如果位域字段之间穿插着非位域字段,则不进行压缩;
       5)
整个结构体的总大小为最宽基本类型成员大小的整数倍。

       
还是让我们来看看例子。

      
4struct A{
                      char f1 : 3;
                     char f2 : 4;
                     char f3 : 5;
                     };

                                 a         b             c
       A
的内存布局:111,    1111 *,   11111 * * *

      
位域类型为char,第1个字节仅能容纳下f1f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2

      
5struct B{
                    char f1 : 3;
                    short f2 : 4;
                    char f3 : 5;
                    };

       
由于相邻位域类型不同,在VC6中其sizeof6,在Dev-C++中为2

      
6struct C{
                     char f1 : 3;
                     char f2;
                    char f3 : 5;
                    };

      
非位域字段穿插在其中,不会产生压缩,在VC6Dev-C++中得到的大小均为3

      
考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。

      
最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间

 




关键词: 位域     对齐    

高工
2014-04-21 14:11:13     打赏
2楼
针对不同的数据类型是否多建个数据结构更节省空间呢?纯属瞎蒙,欢迎指正

院士
2014-04-21 20:33:43     打赏
3楼

在下位机里,一定要字节对齐的。

这个东西可属于C语言高级应用了。

菜鸟 新手可以路过了~~


专家
2014-04-22 09:23:03     打赏
4楼
这个想法是好,我感觉在结构体中,定义的是一个集合,就是为了用起来方便,按照你说的,会不会不太好集合?看看后来的跟帖怎么说

专家
2014-04-22 09:25:42     打赏
5楼
jobs大神,多说两句

高工
2014-05-09 00:13:26     打赏
6楼

新手菜鸟,前来接受熏陶。


专家
2014-05-09 08:50:02     打赏
7楼
共同学习,共同学习

共7条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]