为什么要引入内存对齐?可以提高存储效率,CPU要访问某个数据,可以减少访问次数,提高访问速度,但是速度的提高会损失些内存,后面我们就知道了。
每个编译环境下,都会默认指定一个对齐的值;
每个结构体都有一个自身对齐的值(这个值是结构体内部所有字段中占字节最长的那个字段的尺寸值),如果在VC中结构体
struct tagA
{
char cNum;
int iAge;
char cCount[5];
}A1;
这个结构体int变量占字节最长(数组按基本类型算不能按数组长度算,char cCount[5]还是1)---4个字节,所以这个结构体的自身对齐的值是4,程序在编译分配内存的时候会从默认指定的对齐值和具体该结构体自身对齐值2个值中选出较少的那个值来分配内存,在VC中默认值为8,在Keil中默认值是1
所以A1在VC中分配内存是选用的是min(8,4),在Keil中选用的是min(1,4)来分配A1这个结构体。姑且称这个长度为L
编译的时候,首先选结构体首地址也不是乱选的,首先在内存中选到能够被结构体内部最长那个字段整除的地址
(VC中具体到这个例子就是4的倍数的地址,Keil中就是2的倍数的地址),然后开始依次排字段,结构体内部前面的字段排在靠近该结构体的地址的前面,先排cNum,接着iAge,最后排cCount[5]。
排的时候又有规则,对于基本数据类型长度少于等于L的字段必须排在能被自身长度整除的地址上不连续的部分用空字节补齐(具体到这里iAge这个字段为4,必须排在能被4整除的地址上,但是首地址也是被4整除,cNum只需要1个字节,这个时候在cNum后面就需要补3个空字节,这些空字节就是需要浪费的内存,但是用这些内存换取些效率还是值得的),对于基本数据类型长度大于L的字段必须排在能被L整除的地址上不连续的部分用空字节补齐(具体到这里,如果在Keil环境下L=1,整型字段iAge占2个字节,所以Keil排在能被1整除的任意位置就可以了),最后排完所有字段后如果首地址到最后字段的末尾的总字节数不是L的整数倍,需要用空字节补齐。
这就是字节对齐的所有规则。
另外需要补充一点,虽然编译环境默认有个对齐值,但是可以通过
#pragma pack(2)
struct a
{
char ch;
int i;
short st;
char ch1[3];
}ta;
#pragma pack 2个预处理命令重新设置,#pragma pack(x)表示下面的部分在分配内存时默认值改为x
#pragma pack 表示撤销#pragma pack(x)操作
首先我们知道L=min(2,4)=2(2表示的是#pragma pack(2)被设置为了2,而字段int i占4个字节)
所以char short是小于等于L int是大于L
所以分配内存的时候,首先首地址应该是4的倍数,然后char ch;字段,然后发现int i;字段的尺寸大于L所以对齐在能被L整除的地址上就行,但是char ch;字段只占了1个字节,所以中间补一个字节
ch | |i \ \ \ |
排完i后的地址刚好是偶数,可以拍short st,最后排char ch1[3]; 所有字段排完后发现从首地址到ch1[2]只有11个字节,所以补1个字节使总的字节数能被L整除。
在Keil中默认L=1,所以都是很紧密的排在一起没有空字节,所以总共结构体占8个字节。