内存对齐
内存对齐
首先了解一下各种数据类型所占的内存大小
C/C++ | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (__signed char)int8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int int32_t | NSIneteger(32位)、Boolean_t(32位) | 4 | 4 |
unsigned int | Boolean_t(64位)、NSInteger(32位) | 4 | 4 |
long | NSIneteger(64位) | 4 | 8 |
unsigned long | NSUIneteger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
什么是内存对齐
现代计算机中内存空间都是按照byte划分的,从理论上来说似乎任何的变量访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上进行排列,而不是顺序的一个接一个的排放,这就是对齐
对齐的作用和原因
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
内存对齐的原则
-
数据成员对齐规则:
struct的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员的大小的整数倍开始
-
结构体成员对齐规则:
如果一个结构里有些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始储存
-
结构体的总大小:
就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐
我们举个例子来说明一下:
struct S1 {
char a; //1
double b; //8
int c; //4
short d; //2
}struct1;
struct S2 {
double a; //8
int b; //4
char c; //1
short d; //2
}struct2;
- struct S1
- 变量a:占1字节,从0开始,到1截止,故(0,1)储存变量a
- 变量b:占8字节,从1开始,要从8的整数倍开始存,往后移,故(8,15)储存变量b
- 变量c:占4字节,从16开始。16是4的整数倍。故从16的位置开始,故(16,20)存储变量c。
- 变量d:占2字节,从20开始。20是2的整数倍。故从20的位置开始,故(20,22)存储变量d。
- structS2
- 变量a:占8字节,从0的位置开始,到1截止,故(0,7)存储变量a。
- 变量b:占4字节,从8开始。8是4的整数倍,故(8,11)存储变量b。
- 变量c:占1字节,从12开始。12是1的整数倍。故从12的位置开始,故12存储变量c。
- 变量d:占2字节,从13开始。要从2的整数倍开始存储。故从20的位置开始,故(14,15)存储变量d。
iOS中的内存对齐
Xcode默认的对齐系数为8,下面我们举个例子来理解下iOS中类里面的内存对齐
Person和Student类分别占多少内存
- Person
@interface Person : NSObject
{
int _m;
double _g;
int _d;
char _l;
char _o;
}
@end
@implementation Person
@end
- Student
@interface Student: Person
{
double _agef;
double _nds;
int _nol;
double _ddss;
}
@end
@implementation Student
@end
首先我们看Person,因为Person继承自NSObject,里面有isa指针
所以我们的内存段为8,4,8,4,1,1
因此存储方式为:
8 ---- 4 ---- 4(补齐)----- 8 ----- 4 ------ 1 ------ 1 ----- 2(补齐)
在8到12段我们存放_m,但是当我们要存储内存为8的_g时,12不是8的倍数,因此我们需要从16开始存
又因为结构体的最终大小必须是最大成员大小的倍数,因此4 x 8 = 32,最后补齐两位
所我们得到,Person类为32个内存,实际使用为30个
然后我们再看Student类,Student类继承自Person类
内存段为30,8,8,4,8
因此储存方式为:30 --- 2(补齐)--- 8 --- 8 --- 4 --- 4(补齐)--- 8
我们先存30,因为30不是8的倍数,所以我们补齐,从32开始存,存8,再存8,此时是52位,因为52不是8的倍数,因此我们补齐,从56开始存,到54
需要注意一点的是,在OC中,以上面两个类为例子,Person类的内存分配了32,实际使用30,Student继承后,父类的内存继承下来的就是30,但是如果是纯结构体的话,就是32,这点有所区别。
struct FF
{
double dfsf; // 8
};
struct EE
{
struct FF fd; //8
int _sdfdf; //4
double _fxs; //8
int _sdf; //4
char _df //1
char _fff; //1
};
struct EERRSDF
{
struct EE fsdffd; //32(这里因为是纯结构体,所以就是32了)
char _dfff; //1
double _agef; //8
double _nds; //8
int _nol; //4
double _ddss; //8
};
int main() {
NSLog(@"malloc:%zd",sizeof(struct EERRRSDF));
}
//72