C语言字节对齐
什么是字节对齐?
现代计算机中内存空间都是按照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的工作效率。
字节对齐的概念和规则
概念:
- 数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float类型,其自身对齐值为4,对于double型,其自身对齐值为8(单位字节)。
- 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
- 指定对齐值:
#pragma pack (value)
时的指定对齐值value。 - 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
规则:
有效对齐值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是char类型,自身对齐值就是1字节,b是int类型,自身对齐值是4字节,c是short类型,自身对齐值是2字节
- 结构体的自身对齐值:结构体中自身对齐值最大的那个,根据上面的分析这个结构体的自身对齐值为4字节
- 指定对齐值:因为我们没有指定对齐值,使用的是默认的4字节对齐
- 数据成员有效对齐值:自身对齐值和指定对齐值中小的那个值,a的自身对齐值是1,指定对齐值是4,较小值是1,那么a的对齐值就是1;b的自身对齐值是4,指定对齐值是4,较小值是4,那么a的对齐值就是4;c的自身对齐值是12,指定对齐值是4,较小值是2,那么c的对齐值就是2,假设有一个变量是long类型,那么这个变量的对齐值应该为4.
这样,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字节。