内存对齐:(结构体内部变量、操作系统分配内存)
@interface OffcnPerson : NSObject {
@public
int _age;
NSObject *obj1;
int _no;
NSObject *obj2;
}
#import <malloc/malloc.h>
#import <objc/runtime.h>
- (void)viewDidLoad {
[super viewDidLoad];
OffcnPerson *person = [OffcnPerson new];
NSLog(@"Size of %@:instanceSize-%zd ,mallocSize-%zd", NSStringFromClass([person class]),class_getInstanceSize(OffcnPerson.class), malloc_size((__bridge const void *) person));
}
1、结构体内部变量的内存对齐
具体做法:
1.1、首先以结构体内部,占用字节数最大的变量(比如isa指针)为单位开辟内存空间(8个字节),这8个字节的内存空间放入isa指针。
1.2、再以占用字节数最大的变量(比如isa指针)为单位开辟8个字节空间,放入 _age变量占用4个字节,紧接着要存放obj1对象(8个字节),剩余的4个字节已经不够放obj1对象了。
1.3 、再以占用字节数最大的变量(比如isa指针)为单位开辟8个字节空间,此时obj1就需要做一次内存对齐,不能紧接着(1.2中剩余的4个字节)放入,需要放入新开辟的8个字节的内存空间中。
1.4、再以占用字节数最大的变量(比如isa指针)为单位开辟8个字节空间,放入 _no变量占用4个字节,紧接着要存放obj2对象(8个字节),剩余的4个字节已经不够放obj2对象了。
1.5、再以占用字节数最大的变量(比如isa指针)为单位开辟8个字节空间,此时obj2就需要做一次内存对齐,不能紧接着(1.4中剩余的4个字节)放入,需要放入新开辟的8个字节中,此时已经为person对象开辟了40个字节的空间。
打印结果:
2022-01-25 15:06:10.808147+0800 OffcnApp[34877:1661864] Size of OffcnPerson:instanceSize-40 ,mallocSize-48
1.2、调换变量的顺序,分配的内存空间会变化吗?
@interface OffcnPerson : NSObject {
@public
int _age;
int _no;
NSObject *obj1;
NSObject *obj2;
}
打印结果:
2022-01-25 15:07:55.597185+0800 OffcnApp[34922:1663979] Size of OffcnPerson:instanceSize-32 ,mallocSize-32
1.3、删除变量使用添加属性的方式,分配的内存空间会变化吗?
@interface OffcnPerson : NSObject {
// @public
// int _age;
// int _no;
// NSObject *obj1;
// NSObject *obj2;
}
@property (nonatomic, assign) int age;
@property (nonatomic, strong) NSObject *obj1;
@property (nonatomic, assign) int no;
@property (nonatomic, strong) NSObject *obj2;
打印结果:
2022-01-25 15:11:55.113544+0800 OffcnApp[34994:1667848] Size of OffcnPerson:instanceSize-32 ,mallocSize-32
结论:
结构体变量的内存对齐会以结构体内占用字节最大的变量为单位开辟内存空间,如果本次开辟的内存空间放入变量之后,剩余的内存空间不足以放入下一个变量时,需要重新开辟空间,并且把下一个变量放入新开辟的内存空间里进行一次内存对齐。
如果使用property定义属性的话,会优化变量的存储顺序,尽可能多的使用分配的空间,比如两个int 类型的变量会放在8个连续的字节内。
2、操作系统分配内存时内存对齐
操作系统为对象分配内存的颗粒度是16字节,申请小的内存块时,内存块的颗粒度是16个字节,当你申请4字节时给你分配的是16字节,当你申请24字节时给你分配的32字节,分配的内存是16个字节的倍数。
Allocating Small Memory Blocks Using Malloc
When allocating any small blocks of memory, remember that the granularity for blocks allocated by the malloc library is 16 bytes. Thus, the smallest block of memory you can allocate is 16 bytes and any blocks larger than that are a multiple of 16. For example, if you call
mallocand ask for 4 bytes, it returns a block whose size is 16 bytes; if you request 24 bytes, it returns a block whose size is 32 bytes. Because of this granularity, you should design your data structures carefully and try to make them multiples of 16 bytes whenever possible.
developer.apple.com/library/arc…
为什么要内存对齐?
处理器是如何读取内存的?
大部分处理器并不是按1字节块来存取内存的,这取决于处理器的设置;它一般会以2字节,4字节,8字节,16字节甚至32字节的块来存取内存,我们将上述这些存取单位称为内存存取粒度。
如果对象的变量之间不内存对齐会出现什么情况?
@interface OffcnPerson : NSObject {
@public
int _age;
NSObject *obj;
int _no;
}
- (void)viewDidLoad {
[super viewDidLoad];
OffcnPerson *person = [OffcnPerson new];
person->_age = 30;
person->obj = [NSObject new];
person->_no = 7;
}
图中是OffcnPerson对象的内存分配情况:
1、如果obj向前挪动4个字节(40 D8 5B 02 覆盖前面的 00 00 00 00),那么如果处理器按照结构体占用字节数最大的变量(isa指针)8个字节为一个块读取时,就会在读取从1E开头的字节读取到以02结尾,共8个字节(1E 00 00 00 40 D8 5B 02)。
2、此次读取的8个字节中,前4个字节(1E 00 00 00)读取的是int _age;后4个字节(40 D8 5B 02)读取的是obj的前4个字节。
3、这样就需要在下一次读取中的前4个字节(00 60 00 00)和本次读取的后4个字节( 40 D8 5B 02)需要合并得到obj对象,会严重影响效率。
对于访问未对齐的内存,处理器可能需要进行多次访问;而对于对齐的内存,只需要访问一次就可以。这就是为什么要内存对齐的原因。
对于对齐的内存,不同的存取粒度也会影响到存取效率。
粒度小则存取次数多,粒度大则浪费空间。所以在每个特定平台上的编译器都有自己的默认存取粒度。