下面的代码片段定义了结构体A和B:
- struct A // 结构体A
- {
- int a;
- char b;
- short c;
- };
- struct B // 结构体B
- {
- char b;
- int a;
- short c;
- };
在32位机器上,char、short、int三种类型的大小分别是1、2、4。那么上面两个结构体的大小如何呢?
结构体A中包含了一个4字节的int,一个1字节的char和一个2字节的short,B也一样,所以A、B的大小应该都是4+2+1 = 7字节。但是,实验给出的却是另外的结果:
- sizeof(strcut A) = 8, sizeof(struct B) = 12
其原因还要从字节对齐说起。
现代计算机中内存空间都是按照字节来划分的,从理论上来讲,对变量的访问可以从任何地址开始;但在实际情况中,为了提升存取效率,各类型数据需要按照一定的规则在空间上排列,这使得对某些特定类型的数据只能从某些特定地址开始存取,以空间换取时间,这就是字节对齐。
结构体默认的字节对齐一般满足三个准则:
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
(2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要,编译器会在成员之间加上填充字节(Internal Adding)。
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(Trailing Padding)。
按照这三条规则再去分析结构体A和B,就不会对于上述的结果一脸诧异了。这两个结构体在内存空间中的排列如图1-2所示(灰色网格表示的字节为填充字节)。
图1-2 结构体A和B的内存分布 |
在编程应用中,如果空间紧张,需要考虑节约空间,那么就需要将结构体中的各个变量按照上面的原则进行排列。基本的原则是:把结构体中的变量按照类型大小从小到大依次声明,尽量减少中间的填充字节。
也可以采用保留字节的形式显式地进行字节填充实现对齐,以提高存取效率。其实这就是时间与空间的博弈。如下面的代码片段所示,其中的reserved成员对程序没有什么意义,它只是填补空间以达到字节对齐的目的:
- struct A // 结构体A
- {
- int a;
- char b;
- char reserved; // 保留字节,空间换时间
- short c;
- };
- struct B // 结构体B
- {
- char b;
- char reserved1[3]; // 保留字节1,空间换时间
- int a;
- short c;
- char reserved2[2]; // 保留字节2,空间换时间
- };
在某些时候,还可以通过编译器的pack指令调整结构体的对齐方式。#pragma pack的基本用法为:
- #pragma pack( n )
- n为字节对齐数,其取值为1、2、4、8、16,默认是8。
- #pragma pack(1)// 设置1字节对齐
- struct A // 结构体A
- {
- int a;
- char b;
- short c;
- };
将结构体A的对齐方式设为1字节对齐,那么A就不再有填充字节了,sizeof(A)的结果即为各元素所占字节之和7。
请记住:
了解结构体中元素的对齐规则,合理地为结构体元素进行布局。这样不仅可以有效地节约空间,还可以提高元素的存取效率。
转载于:https://blog.51cto.com/rainseason/819128