结构体与位域的使用

C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?

开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐?

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

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

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

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

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

例1:struct  {
short  a1 ;
short  a2 ;
short  a3 ;
}A;

struct{
long  a1;
short  a2 ;
}B;

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

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

例2:struct  A{
int   a;
char   b;
short  c;
};

struct  B{
char  b;
int a;
short  c;
};

sizeof(A) = 8; int为4,char 为1,short 为2,这里用到了原则1和原则 3。

sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

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

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

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

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

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

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

例3:struct A{
int a;
double  b;
float  c;
};

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

sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

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  pac k宏,对齐方式按照宏的定义来。比如上面 的结构体前加#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规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型 的存在。

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

还是让我们来看看例子。
例4:struct   A{ 
char  f1  : 3; 
char f2 : 4; 
char f3 : 5; 
};

a b c
A的内存布局:111 , 1111  *, 11111  * * *
位域类型为char,第1个字节仅能容纳下 f1 和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。

例5:struct B{ 
char  f1  : 3; 
short f2 : 4; 
char  f3 : 5; 
};
由于相邻位域类型不同,在VC6中其sizeof 为6,在 Dev-C++ 中为2。

例6:struct C{ 
char f1 : 3; 
char f2; 
char f3 : 5; 
};
非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。


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

最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对 节约一些对齐空间。
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值