内存字节对齐详解

每次找工作,面对如海的面试题,都能感觉到深深的无力。在C++的面试题中内存字节对齐也是一个老生常谈的话题了,下面就来解析一下内存对齐的原因与计算方式。

一、内存字节对齐的原因

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

二、sizeof()操作符

说道内存对齐,那么必不可少的就是sizeof()操作符,简单的说其作用是返回一个对象或者类型所占的内存字节数。其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为  typedef unsigned int size_t;世上编译器林林总总,但作为一个规范,它们都会保证char、signed char和unsigned char的sizeof值为1,毕竟char是我们编程能用的最小数据类型。

用法:

sizeof(类型)                               eg:sizeof(int)

sizeof(变量名)   eg:int a=10; sizeof(a)

sizeof 变量名 eg:int a=10; sizeof a;

关于对变量名的sizeof操作,以上提到了两种方式,为了代码风格的统一,建议使用的使用采用sizeof(变量名)的方式。

1、若操作数具有类型char、unsigned char或signed char,其结果等于1。ANSI C正式规定字符类型为1字节。       

2、int、short、long、float、double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,在32位VS的编译器中分别为4、2、4、4、8。       

3、当操作数是指针时,sizeof依赖于编译器,一般指针字节数为4。       

4、当操作数具有数组类型时,其结果是数组的总字节数。    

eg:char szArray[10] = "1123"; a=sizeof(szArray); 这是a的值是10,而不是4哦 。

  5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。       

让我们看如下结构:       

struct 

{

char b; 

double x;

} a;      

 在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。 这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。 具体的后面内存对齐会讲到。     

 6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

eg:void Fun(char szArray[100])
{
size_t a= sizeof(szArray) ;//a的值是4而不是100哦
}

三、内存对齐计算方法

先来看一个结构体例子:

struct 
{
double d;
int   n;
char   c;
}TestStruct;

对于这个结构体求sizeof等于多少呢?一般来说我们都会这样得出答案sizeof(TestStruct)=sizeof(double)+sizeof(int)+sizeof(char) =8+4+1=13;但是在VS编辑器中测试得出的结果却是16。这是为什么呢?其实,这是VS对变量存储的一个特殊处理。为了提高CPU的存储速度,VS对一些变量的起始地址做了“对齐”处理。在默认情况下,VS规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VS会自动填充。同时VS为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。  下面用前面的例子来说明VS到底怎么样来存放结构的。

首先为第一个成员d分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员n分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(int)的倍数,所以把n存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(int)=4个字节;接下来为第三个成员c分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为12,是sizeof (char)=1的倍数,所以把c存放在偏移量为12的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节,总的占用的空间大小为:8+4+1=13,现在这个总大小不满足为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以需要填充3个字节。所以整个结构的大小为:sizeof(MyStruct)=8+4+ 1+3=16,其中有3个字节是VS自动填充的,没有放任何有意义的东西。

让我们再来看一个更有意思的东西,将上面结构体的成员声明顺序交换一下,变成下面这样:

struct 
{
int   n;
double d;
char   c;
}TestStruct;

这个结构体的sizeof(TestStruct)等于多少呢?按照上面的方法,自己算一算,结果是24,你算对了吗?

四、自定义对齐方式

VS对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。  VS 中提供了#pragma pack(n)来设定变量以n字节对齐方式。

n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

1、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;

2、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

结构的总大小也分下面两种情况:

1、如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 

2、 否则必须为n的倍数。

看下面一个例子

#pragma pack(push)	//保存对齐状态  
#pragma pack(4)		//设定为4字节对齐   
struct 
{
	int	n;
	double	d;
	char	c;
}TestStruct;
#pragma pack(pop)	//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为n分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),n占用4个字节。接着开始为 d分配空间,这时其偏移量为4,偏移量满足为n=4的倍数(因为sizeof(double)大于n),d占用8个字节。接着为c分配空间,这时其偏移量为12,满足为4的倍数,c占用1个字节。这时已经为所有成员变量分配了空间,共分配了13个字节,不满足为n的倍数,所以补充3个字节,变成16,为4的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。

#pragma pack(4)满足的是两种情况中的第二种,使用n的倍数的方式来偏移。#pragma pack(16)满足的是两种情况中的第一种,使用默认对齐方式。

上面内容就是简单的介绍了一下内存对齐的计算,在实际运用中,大家还是要多多动手,多写一些结构体,自己按照上面的方式算算,最后再用编译器验证自己的答案。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值