现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
字节是否对齐关系到CPU访问数据时的效率问题,假设一个CPU每次总是从内存中取出4个字节,从内存编号为0的地方开始,现在我定义一个char a,定义一个int b,让他们按顺排列在内存中,就是这样的:
char a占用1个字节,int a占用4个字节,CPU每次总是取4个字节,这时我想要取b时,需要先取出0-3,再取出4-7,然后将1-4拼在一起,这样就需要取两次,但是,如果我让char a和int b按照特定的顺序排列:
这样我只需要取一次就能将b取出,提升了CPU的工作效率。
概念:
规则:
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。
有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0"。而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整。
代码如下
#include <stdio.h>
struct Test
{
char a;
int b;
short c;
};
int main(void)
{
struct Test t = {'a',1,2};
printf("%d \n",sizeof(t));
getchar();
return 0;
}
如果我们不知道字节对齐规则,那么一定会认为这个结构体的大小是这样的,char类型1个字节,int类型4个字节,short类型2个字节一共7个字节,执行一下看看结果:
执行之后的结果是12,我们先来看一下反汇编代码:
根据反汇编代码我们可以看到a,b,c中的值分别存放在ebp-0ch,ebp-8,ebp-4这三个地方,我们到内存中看一下他们是怎么排列的:
可以看到char占用1个字节,int占用4个字节,short占用2个字节,但是并没有我们想象的那样紧挨着排放,而是有一定的排放规则。这里就体现出了字节对齐,因为我这里是32位的机器,默认是4字节对齐,下面来详细的说一下是怎么排列的:
假设基址为0012FF3C,从偏移地址为0的位置开始存放
这样,a占用1个字节,存放在偏移地址为0的内存,0%1=0,没有问题。
b占用4个字节,如果将他挨着变量a存放,也就是存放在偏移地址为1的位置,1%4=1,这样就存在问题了,所以b存放到偏移地址为4的位置,4%4=0。
最后,c占用2个字节,如果将他挨着b存放,也就是存放到偏移地址为8的位置,8%2=0,没有问题,所以c存放到 偏移地址为8的位置,占两个字节。
这样就排列好了,这时,发现a,b,c一共才占用了10个字节,因为结构体还没有根据自身有效对齐值圆整,根据上面的分析,这个结构体的自身对齐值是4,12%4=0,所以结构体会再占用两个字节,10和11,也就是A和B,这样就得出这个结构体的实际大小是12。
当然,除了使用默认的对齐值,我们还可以自己使用#pragma pack (value)指定对齐值:
#include <stdio.h>
#pragma pack (2)
struct Test
{
char a;
int b;
short c;
};
int main(void)
{
struct Test t = {'a',1,2};
printf("%d \n",sizeof(t));
getchar();
return 0;
}
这将对齐值设置成2,那么a,b,c在内存中的排列方式应该是这样的:
要注意,这里的int b的自身对齐值是4字节,有效对齐值是2字节。