结构体的内存对齐现象

本篇主要介绍如何计算结构体的大小,这涉及到一个特别热门的考点:结构体内存对齐。

为什么存在内存对齐?

大部分的参考资料都是这么说的:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。

下面先来了解两个概念

1.自身类型有一个对齐值
例如,int类型自身的对齐值就是4,short类型自身的对齐值是2,double类型自身的对齐值是8等等。所谓对齐值,就是要使得某个类型变量所占用的空间大小是对齐值的整数倍,这样的行为成为“对齐”。

2.自定义类型有一个对齐值(内部成员最大的一个)
自定义类型例如结构体(struct),联合体(也称共用体,union),枚举(enum),都有其对应的对齐值,其值等于其内部成员最大的一个类型的大小。

下面来看例子一:下面的代码创建了一个结构体,其内部成员有char类型的a,double类型的b,int类型的c;因此,d自身的对齐值是1,b自身的对齐值是8,c自身的对齐值是4.结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;如下,a自身的对齐值是1,而b的对齐值是8,由于要求结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,从结构体首地址(也是a的首地址)到b的首地址之间需要补够八个字节,即1+7,本质上填补上的7个空间并不需要存储任何数据,但会在CPU的访存过程提供极大的便利,获取运行速度上的优势,这便是所谓“空间换时间的概念”;好的代码往往是占用空间小,运行速度快的,但值得一提的是,空间与时间往往不可兼得,因此程序员需要在两者之间取其平衡点。
再往下,同理,c自身的对齐值是4,相对于结构体首地址的偏移量目前是1+7+8=16个字节,由于16是4的整数倍,因此b的存储空间到c的存储空间之间无需再补字节,再往下看,就要用到第二个概念:自定义类型有一个对齐值(内部成员最大的一个),目前为止结构体的总大小为1+7+8+4=20,但其对齐值为其内部成员最大的一个(即double类型,8),20不为8的整数倍,需要在c后再向上补4个字节到24.因此该结构体所占字节数为(1+7)+(8)+(4+4)=24个字节。

typedef struct Test
{
	char a;   //1     1 + 7
	double b; //8     8
	int c;    //4     4 + 4
}Test;   

剩下两个重要概念

3 程序有一个指定的对齐值(#pragma pack(n) 2^x)
每个特定平台上的编译器都有自己的默认对齐值。你也可以通过预编译命令#pragma pack(n),n为2的整数幂次方,来改变这一系数,其中的n就是你要指定的对齐值。

4 程序有一个有效对齐值(较小值)
在通过预编译命令#pragma pack(n)规定了指定的对齐值后,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行,较小的对齐值被称为有效对齐值。可预见到,当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。一般编译器的默认的对齐值为最大类型的大小。

例二:在遇到结构体嵌套结构体时,先计算内部结构体的大小。
这里利用#pragma pack(2)指定了对齐值为2
b为一个有十个元素的数组,一个元素为一个int类型,因此b的总大小为40,各个元素的相对于该结构体首地址的偏移量均为有效对齐值(2)的整数倍。对于c而言,其偏移量40也为2的整数倍,d为char类型,须考虑整个结构体的大小为40+8+1=49,其大小不为2的整数倍,因此在d后补一个字节,则内部的结构体的总大小为50;
再来看最外面的结构体:a是short类型,占两个字节,为2的整数倍;再往下看,内部的结构体占50个字节;e的偏移量为52,也为2的整数倍,因此外补结构体Test的大小为2+50+4=56.
值得一提的是,外部结构体中最大的元素为double类型,而不是内部结构体。如果没有规定#pragma pack(2),struct a的偏移量应为c的整数倍(其自身对齐值是其内部最大的元素的大小)。

#pragma pack(2)

typedef struct Test
{
	short a;  //2
	struct
	{
		int b[10];//40
		double c; //8
		char d;   //1 + 1
	};
	long e;  //4 
}Test;  

例三:S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。大家可以自己分析一下原因,或者在自己的编译器上运行一下。

struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};

这个例子告诉我们,在设计结构体的时候,我们既要满足对齐,又要节省空间,
如何做到:
让占用空间小的成员尽量集中在一起。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值