我们可以先看看下面的结构体,观察一下结构体的内存分配情况:
struct Struct1 {
int a;
double b;
int c;
char d;
short e;
}myStruct1;
struct Struct2 {
int a;
double b;
char d;
int c;
short e;
}myStruct2;
NSLog(@"%lu - %lu - %lu",sizeof(myStruct1),sizeof(myStruct2));
打印结果 24 - 32
struct Struct3 {
double b;
char d;
int c;
short e;
struct Struct2 myStruct2;
}myStruct3;
NSLog(@"%lu",sizeof(myStruct3));
打印结果 56
struct Struct4 {
int c;
}myStruct4;
struct Struct5 {
double b;
char d;
int c;
short e;
struct Struct4 myStruct4;
}myStruct5;
NSLog(@"%lu",sizeof(myStruct5));
打印结果 24
比较Struct1 和 Struct2 属性是相同的,但是属性的位置有一定的区别。这就是内存对齐的现象。
比较Struct3 和 Struct5的区别,Struct2的属性里面的最大是8,Struct4里面最大是4
内存对齐的规则
每个特定的平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。我们可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是要指定的“对齐系数”。Xcode的对齐系数就是8。
我们在了解内存对齐之前,先看看内存对齐的原则:
- 数据成员对齐规则:(Struct 或 union 的数据成员)第一个数据成员放在偏移为0的位置,以后每个成员的偏移为 min(对齐系数,自身长度)的整数倍,不够整数倍的补齐。
- 数据成员为结构体:该数据成员的自身长度为其最大长度的整数倍开始存储
- 整体对齐规则:数据成员按照上述规则对齐之后,其本身也要对齐,
对齐原则是min(对其系数,成员最大长度)的整数倍。
通过这个规则我们对Struct5做一下简单的描述
double b 8字节 存储区间 [0-7];
char d 1字节 存储区间[8-9];
int c 4字节 还有[10-15]存储段 保持4的倍数 存储区间[11-16];
short e 2字节 存储区间[17-18];
接下来 struct Struct4 myStruct4;
int c 4字节 有此仅有一个 需要的4的倍数的位置 存储区间[19-23];
保证整体对齐规则 min(对其系数,成员最大长度)为8的整数倍 即24字节
内存对齐原因
内存对齐是编译器帮我们处理的。但一个程序要求CPU读取未对齐的数据时,CPU会进入异常处理状态并且通知程序不能继续执行。因为未对齐的数据,会大大的降低CPU的性能。
CPU并不是以字节为单位来存取数据的,它会把内存当成一块一块的来,块的大小可以是2、4、8、16、32字节,每次读取都是一个固定的开销,减少内存存取次数将提升程序的性能。
我们可以假设 CPU先从地址0 读取4字节到寄存器,这时候读取的是对齐地址的数据,但从地址1读取的时候是非对齐的数据,就可能需要读取几次才能完成,然后在合成之后到寄存器。
OC内存对齐
@interface XDPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic) char ch1;
@property (nonatomic) char ch2;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
XDPerson *p1 = [XDPerson alloc];
p1.name = @"xiedong";
p1.age = 18;
p1.height = 180;
p1.sex = @"男";
p1.ch1 = 'a';
p1.ch2 = 'b';
NSLog(@"%lu - %lu",class_getInstanceSize([p1 class]),malloc_size((__bridge const void *)(p1)));
}
x/6gx p1打印结果
(lldb) x/6xg p1
0x600000ce0000: 0x00000001029570d0 0x0000001200006261
0x600000ce0010: 0x0000000102956098 0x00000000000000b4
0x600000ce0020: 0x00000001029560b8 0x0000000000000000
(lldb) po 0x00000001029570d0
XDPerson
(lldb) po 0x00000012
18
(lldb) po 0x62
98
(lldb) po 0x61
97
(lldb) po 0x0000000102956098
xiedong
(lldb) po 0x00000000000000b4
180
(lldb) po 0x00000001029560b8
男
NSLog打印的结果 40-48
- 可以猜到objc帮我们的对象属性做了优化处理,把size大小比较的小的 int char 组合在了一起.
- 然后会发现对象申请的内存空间<=系统开辟的内存空间.
通过alloc的流程 我们会在objc源码里面看对齐方式是以8字节对齐的。
通过calloc的流程 我们可以在malloc的源码的segregated_size_to_fit()函数里面可以看到对齐方式是以16字节对齐的。
我们可以发现 对象内部的属性有自己的内存空间是保证安全的,那么对象与对象之间是怎么保证安全的呢?
XDPerson对象内部的属性+isa占用的内存空间是28+8=36, 实际申请的内存是40 。
那么多余申请的4字节是否可以保证对象与对象之间的安全(或者说这多余的4字节是两个对象地址之间空出来的那一段呢)
结果并不是的,这多余的4字节可能是在这个对象内存空间的某个位置,并不一定在最后面。
所以系统为了保证更加的安全,以16字节对齐的方式开辟对象的内存空间,保证对象的内存空间更大,对象与对象之间更加安全。