关于内存对齐
一:
1.什么是内存对齐
假设我们同时声明两个变量:
char a;
short b;
用&(取地址符号)观察变量a,
b的地址的话,我们会发现(以16位CPU为例):
如果a的地址是0x0000,那么b的地址将会是0x0002或者是0x0004。
那么就出现这样一个问题:0x0001这个地址没有被使用,那它干什么去了?答案就是它确实没被使用。因为CPU每次都是从以2字节(16位CPU)或是4字节(32位CPU)的整数倍的内存地址中读进数据的。如果变量b的地址是0x0001的话,那么CPU就需要先从0x0000中读取一个short,取它的高8位放入b的低8位,然后再从0x0002中读取下一个short,取它的低8位放入b的高8位中,这样的话,为了获得b的值,CPU需要进行了两次读操作。
但是如果b的地址为0x0002,那么CPU只需一次读操作就可以获得b的值了。所以编译器为了优化代码,往往会根据变量的大小,将其指定到合适的位置,即称为内存对齐(对变量b做内存对齐,a、b之间的内存被浪费,a并未多占内存)。
2.结构体内存对齐规则
结构体所占用的内存与其成员在结构体中的声明顺序有关,其成员的内存对齐规则如下:
(1)每个成员分别按自己的对齐字节数和PPB(指定的对齐字节数,32位机默认为4)两个字节数最小的那个对齐,这样可以最小化长度。
(2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。
(3)结构体对齐后的长度必须是成员中最大的对齐参数(PPB)的整数倍,这样在处理数组时可以保证每一项都边界对齐。
(4)计算结构体的内存大小时,应该列出每个成员的偏移地址,则其长度=最后一个成员的偏移地址+最后一个成员数的长度+最后一个成员的调整参数(考虑PPB)。
下面举例说明上述规则:
#include #pragma pack(2) //指定PPB为2 struct T { char a; //偏移地址0 int b; //偏移地址2 char c; //偏移地址6 }; #pragma pack() //恢复原来默认PPB,32位下为4 int main(int argc,char * argv[]) { printf("sizeof(struct T)); return 0; }
最后输出的结果为:8。语句#pragma pack(2)的作用是指定结构体按2字节对齐,即PPB=2。分析如下:
变量a默认为1字节,PB=2,所以a按1字节对齐,a的偏移地址为0。
变量b默认为4字节(在32位机器中int为4字节),PB=2,所以b按2字节对齐,b的偏移地址为2。
变量c默认为1字节,PB=2,所以c按1字节对齐,偏移地址为6。
此时结构体的计算出的字节数为7个字节。最后按规则3,结构体对齐后的字节数为8。sizeof(T)=6+1+1=8
3.范例
(1)#pragma pack(2) //指定PPB为2
struct T { char a; //偏移地址0 char b; //偏移地址1 int c; //偏移地址2 };
则sizeof(T)=最后一个成员的偏移地址+最后一个成员数的长度=2+4=6。
struct T1 { char a; //偏移地址0 char b; //偏移地址1 int c; //偏移地址4 }; struct T2 { char a; //偏移地址0 int b; //偏移地址4 char c; //偏移地址8 };
PPB=4,则sizeof(T1)=4+4=8;sizeof(T2)=8+1=9,9不能整除4,故调整数为3,即sizeof(T2)=8+1+3=12
4.注意的问题
(1)字节对齐取决于编译器;
(2)一定要注意PPB大小,PPB大小由pragam pack(n)指定;
(3)结构体占用的字节数要能被PPB整除。
二:
(1)sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用。
(2)终于搞懂struct结构体内存分配问题了,结构体中各个成员字节对齐遵循以下几个原则: 直接用下面几个原则即可判断结构体的大小
1.结构体每个成员相对于结构体首地址的偏移量(offset)都是(这个)成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internaladding);
例如有以下一个结构体:
structex { int i; char t; int n; };
第1个成员偏移量为0,是int型成员大小4(假设这太机器的整型长度占4个字节)的整数倍。
第2个成员t为char型,他的大小为1,首先假设在成员i和t之间没有填充字节,由于i是整型,占4个字节那么在没有填充之前,第2个成员t相对于结构体的偏移量为4,他是t成员大小1的4倍,符合此条件,所以系统在给结构体第2个成员分配内存时,不会在i和t之间填充字节以到达对齐的目的。
当分配结构体第3个成员n时,首先发现是一个整型数据,大小为4,没有填充之前,n相对于结构体首地址偏移量为:前面2个成员+填充字节=5,所以当系统发现5不是4(成员大小)的整数倍时,会在成员t之后(或者说n之前)填充3个字节,以使n的偏移量到达8而成为4的整数倍。这样这个结构体占用内存情况暂时为4+1+3+4。
2.结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailingpadding)。
上面的结构体内存分配以后还要看是否满足此条件,假设在最末一个成员之后不需填充字节数,那么这个结构体的大小为12。而ex结构体中最宽基本类型成员为int,大小为4,12为4的整数倍,所以无须再在最末一个成员之后加上填充字节了。所以sizeof(ex)=12;
如果一个结构体如下所示:
struc test1 { int i; char t; int n; char add; };
那么sizeof(ex1)=16;原因就是在最后一个成员之后填充了3个字节。
3.还有一个额外的条件:结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
4.对于结构体成员属性中包含结构体变量的复合型结构体再确定最宽基本类型成员时,应当包括复合类型成员的子成员。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
5总结出一个公式:结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) +sizeof( trailing padding )