1、为什么要对结构体内存对齐?
了解计算机的人都知道,CPU是没有办法来存储数据的,CPU只有计算能力。所以,存储数据的任务就交给了内存。那么,当CPU需要计算的时候,就需要从内存上获取数据,内存上数据和地址是对应的,CPU只要知道对应数据的地址,就可以调用数据了。
知道了CPU必须寻址之后,我们来看一下一个结构体是怎么定义的。
struct s1
{
char c1;
int i;
char c2;
}
我们来通过一个图看一下,没有进行内存对齐的时候,CPU是怎么进行寻址的。
前提:硬件假设-必须从4的整数倍地址处进行寻址。
这里,我们要对c2寻址的话,直接寻址即可。可是如果要对i进行寻址呢,因为寻址地址必须是4的整数倍,所以先从0x04寻址,到0x07之后结束,还必须减去c2的地址,这是i的前半部分;并且由于i没有完全得到,还必须从0x08开始,继续4个字节,然后减去0x09之后不包括i数据的地址,然后得到i的后半部分,再把前半部分和后半部分拼接得到i。由此可见,寻址效率非常低下,所以必须要进行内存对齐,从而提高寻址效率。
2、如何进行内存对齐呢?
内存对齐的规则
(1)第一个成员在与结构体变量偏移量为0的地址处。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注:起始偏移量能整除对齐数,即为对齐。
(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。注:结构体内所有成员各自对齐数的最大值(包括第一个)。
(4)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
(5)数组的对齐数就是数组内元素的类型字节大小。
(6)结构体对齐数是结构体的最大对齐数。
3、举例理解
知道了内存对齐的规则之后,我们来通过两个例子明白如何使用上面的规则。
例1、
struct s1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct s1));
我们可以看到,结构体s1的字节大小是12。为什么呢?我们来解释一下。
先给c1开辟空间,直接开辟1个字节,根据规则1,偏移量是1字节。然后给i开辟空间,这里就不能直接开辟了,通过规则2可知,偏移量必须能整除i的对齐数,而i的对齐数是4现在偏移量是1,所以就再开辟3个字节的空间,现在就可以给i开辟空间了,因为此时偏移量是4,能整除i的对齐数,然后在4个字节的偏移量基础上给i开辟4个字节空间,现在就一共开辟了8个字节的空间。最后给c2开辟空间,此时偏移量是8,可以整除c2的对齐数1,所以直接开辟c2的空间,1个字节,所以是8加1等于9个字节。然而根据规则3,结构体的大小必须能整除最大对齐数,而此时的最大对齐数是4,又因为9不能整除4,所以接着开辟3个字节,9加3等于12。所以结构体s1的字节大小就是12。
例2、
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
struct s1 s1;
double d;
};
printf("%d\n", sizeof(struct s2));
这里s2里面嵌套了一个结构体s1,根据规则4可知,结构体的对齐数是它的最大对齐数,就s1而言,s1的对齐数就是4,s1的大小我们已经计算出是12字节。现在,我们看s2,首先给c1开辟空间,1个字节。然后给s1开辟空间,此时偏移量是1,s1的对齐数是4,不能整除,所以给偏移量加3,偏移量变为4,可以整除,再给s1开辟空间,4加12是16字节。最后给d开辟空间,d的对齐数是8,大小是8字节,此时偏移量是16,可以整除对齐数8,所以直接给d开辟空间,8加16是24字节,根据规则3,s4的最大对齐数是8,因为24能整除8,所以s2的大小就是24字节。
总结:
a、要理解对齐数的概念,所谓对齐数就是变量本身字节的大小。对于结构体和数组则不是,结构体的对齐数是最大对齐数,就是结构体内所有变量对齐数最大的那个。数组则是数组内的元素类型字节大小。
b、计算结构体字节大小时,不可以上来就给变量开辟空间,需要看此时的偏移量能否整除掉此时要开辟空间的变量的对齐数,如果能整除,就直接开辟。如果不能整除,就加字节空间,加到可以整除,再开辟。
c、结构体的大小计算完毕后,还需要看是不是能整除结构体的最大对齐数。如果能整除,那么此时计算出的大小就是结构体的大小,如果不是,就加字节空间,加到可以整除,这时才是结构体的大小。
d、数组结构体的最大对齐数是结构体的最大对齐数。
4、内存对齐到底是什么?
由于硬件条件的限制,cpu只能按4个字节进行寻址。如果不进行内存对齐,就会出现寻址效率低下的问题。总体来说:内存对齐就是以牺牲空间为代价,换取时间,提高寻址效率。