简述
今天在优快云上看到一题腾讯的面试题,题目是这样的:
struct Test{
int a;
char b;
short c;
};
问:
sizeof(Test)=?
Test test;
sizeof(test)=?
我们在编译器中输出结果可以看到:
我们都知道,一般int占4字节,char占1个字节,short占2个字节,结果不是应该是7个字节吗?为什么实际输出会是8个字节呢?这是因为在计算机中内存空间里各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放。结构体的大小和数组这种存储一种数据类型的不一样,并不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的字节对齐问题。
相关概念
自身对齐值:
不同的硬件平台对存储空间的处理上有很大的不同,在32位操作系统中,默认对齐值是4,在64位操作系统中,默认对齐值是8,各数据类型所占字节数分别为:
- int占4个字节
- char占1个字节
- short占2个字节
- float占4个字节
- double占8个字节
这里每种数据类型所占字节数被我们称为数据的自身对齐值,所以int的自身对齐值为4,char的为1,short的为2,float的为4,double的为8。结构体或者类的自身对齐值为其成员中自身对齐值最大的那个值。
指定对齐值:
系统默认的对齐值为指定对齐值,也可以用#pragma pack (value)语句,指定对齐值value。
数据成员、结构体和类有效对齐值:
数据成员、结构体和类的有效对齐值为自身对齐值和指定对齐值中小的那个值。
偏移量:
偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。因此,第一个成员的偏移量为0。第二个成员的偏移量是第一个成员的偏移量加上第一个成员的大小,相同的,第三个成员的偏移量是第二个成员的偏移量加上第二个成员的大小,每一个成员的偏移量都是上一个成员的偏移量加上上一个成员的大小。
对齐规则
字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
- 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
- 结构体每个成员相对于结构体首地址的偏移量都是该成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。
- 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整。
具体例子
知道了上面的概念和对齐规则我们就可以看看一开始的题目里结果为什么会是8了。
struct Test{
int a; //a的偏移量为0,int自身对齐值为4
char b; //b的偏移量为4,char自身对齐值为1
short c; //c的偏移量为5,short自身对齐值为2,不符合我们上面的第二条,偏移量5不可被2整除
//所以编译器会在b后面补上一个空字节,这样偏移量为6,c占2字节,整个结构体Test大小为8个字节
};
我们再来看一个:
struct Test1
{
char a; //a的偏移量为0,char的自身对齐值为1
int b; //b的偏移量为1,int的自身对齐值为4,因为4不能被1整除,自动在a后面添加3个空字节,添加后b的偏移量为4
short c; //c的偏移量为8,short的自身对齐值为2,这时候整个结构体占10个字节
//但是该结构体的自身对齐值为其成员中自身对齐值最大的4,所以它的有效对齐值也为4,10不能被4整除
//所以编译器会在c后面补上两个空字节,整个结构体的大小为12
};
从上面两个例子中,我们可以发现Test和Test1两个结构体中的成员是一样的,都是int、char、short三种数据类型。但是由于书写的顺序不同,结构体的大小也不一样,一个是12,一个是8。
可见结构体中成员的书写顺序对结构体大小的影响还是很大的,一个好的建议是,按照数据类型由小到大的顺序进行书写。
如果结构体中的成员又是另外一种结构体类型时应该怎么计算呢?只需把其展开即可。但有一点需要注意,展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍:
struct Test2
{
short i; //偏移量为0,short自身对应值为2
struct
{
char n; //因为结构体test的自身对齐值为4,所以test.n的偏移量应该是4,而不是2
int m; //偏移量为8,
} test;
int j; //偏移量为12,所以整个结构体大小是16
}
查看结构体的对齐值
使用预处理命令:
#pragma pack(show)
该命令来查看当前的对齐值,但是要注意的是,结果是以warning的形式输出的,所以要在VS的警告窗口中才看得见,如下:
warning C4810: value of pragma pack(show) == 8
使用预处理命令:
#pragma pack(num)
num是结构体的对齐值,可以指定接下来的结构体的指定对齐值,可以这样使用:
#pragma pack (2) //指定按2字节对齐
struct Test
{
char a;
int b;
short c;
};
#pragma pack () //取消指定对齐,恢复缺省对齐