1、什么是内存对齐?
下面先来看一个例子:
#include <stdio.h>
int main()
{
struct Text1
{
char c1;
short s;
char c2;
int i;
};
struct Text2
{
char c1;
char c2;
short s;
int i;
};
printf("%ld\n",sizeof(struct Text1)); //12 GCC下编译的结果
printf("%ld\n",sizeof(struct Text2)); //8
return 0;
}
解析:
在windows系统32位平台上:
int占4个字节
char占1个字节
float占4个字节
double占8个字节
1、首先确定对齐参数:对齐参数是由变量的自身对齐参数和编译器默认的对齐参数一起确定的!它是结构体中所有变量的自身对齐参数的最大值和系统默认参数中较小的那一个!(含有double类型的除外,原因如下!)而变量的自身对齐参数是由CPU字节长度(32位为4)和变量本身的字节长度中的较小的那一个(即变量的自身对齐参数<=4),所以本题中的对齐参数为4!
2、知道了对齐参数为4,即知道了分配单位就是4!
这里需注意的是:
VC和GCC默认的都是4字节对齐,编程中可以使用#pragma pack(n)指定对齐模数。出现以上差异的原因在于,VC和GCC中对于double类型的对齐方式不同。
Win32平台下的微软VC编译器在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。
Linux下的GCC奉行的是另外一套规则:任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数!
#prama pack()能改变编译器的默认对齐方式!例如
#pragma pack(n) //编译器将按照n字节对齐
#pragma pack() //编译器将取消自定义字节对齐方式
在#pragma pack(n)和#pragma pack()之间的代码按n字节对齐! 此为强制对齐!
2、为什么会有内存对齐?
字、双字和四字在自然边界上不需要在内存中对齐。对字、双字和四字来说,自然边界分别是偶数地址、可以被4整除的地址和可以被8整除的地址。无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;然而,对齐的内存访问仅需要进行一次访问。
一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问中未对齐的数据。
补充:
自然对齐指的是将对应变量类型存入对应地址值的内存空间,即数据要根据其数据类型存放到以其数据类型为倍数的地址处。例如char类型占1个字节空间,1的倍数是所有数,因此可以放置在任何允许地址处,而int类型占4个字节空间,以4为倍数的地址就有0,4,8等。编译器会优先按照自然对齐进行数据地址分配