2020-01-29 11:11

浅谈OC的内存对齐

摘要

:内存对齐是一个编译器的行为,可以有效的提升效率。本文主要是探究OC的内存对齐的相关规则。

作用

内存对齐主要的作用是为了方便CPU在访问内存数据时更快速的进行访问

探究对象以及对象属性

相关代码如下,新建一个名为Test,基于NSObject的类,添加一些属性

#import <Foundation/Foundation.h>

struct SIMPLE {
    int e;
    char f;
    double g;
};

@interface Test : NSObject

@property (nonatomic, strong) NSString *a;
@property (nonatomic, assign) int b;
@property (nonatomic, assign) long c;
@property (nonatomic, assign) struct SIMPLE *d;

@end

然后实例化并且对他进行赋值,同时打印对象的 class_getInstanceSize 方法以及 malloc_size 方法

头文件如下

#import "Test.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

代码如下

Test *t = [[Test alloc] init];
t.a = @"hello";
t.b = 1;
t.c = 10000;
struct SIMPLE s;
s.e = 1;
s.f = 'a';
s.g = 2;
t.d = &(s);
NSLog(@"%lu - %lu", class_getInstanceSize([t class]) , malloc_size((__bridge const void *)(t)));

可以看到打印结果为 class_getInstanceSize 的值为 40, 而 malloc_size的值为48

image

先通过lldb探究一下内存实际占用

image

上图我们以8个字节为一段,打印了t对象的连续6段内存地址

可以看到,第一段的内存是isa,占用8个字节

第二段则是我们的int类型的属性b,int类型的数据应该占用4字节的空间(以下如无特殊说明,空间占用均为在64位操作系统下的占用),但是由于对齐的缘故,该属性占满了8个字节的内存空间,也就是说,属性是以8个字节进行对齐的

第三段是NSString类型的属性a,占用8个字节

第四段是long类型的属性c,占用8个字节

第五段是名为SIMPLE的结构体属性d,也是占用8个字节

第六段是空值,也就是一共占用了五段内存空间,一共40个字节。和 class_getInstanceSize 得到的值一样,因此 class_getInstanceSize 是获取对象实际占用的内存空间的值

那么 malloc_size 的值为48, alloc是系统开辟内存空间的方法,也就是说,我们实际只需要40字节的空间大小,但是系统给我们开辟了48字节的空间。因为对于对象来说,系统是按照16个字节进行对齐的,malloc_size 是获取系统开辟的内存空间大小

探究结构体

新建如下三个结构体

struct Struct1 {
    char a;
    double b;
    int c;
    short d;
} MyStruct1;

struct Struct2 {
    double b;
    int c;
    short d;
    char a;
} MyStruct2;


struct Struct3 {
    char a;
    int c;
    short d;
    double b;
} MyStruct3;

打印他们的内存占用

NSLog(@"%lu-%lu-%lu", sizeof(MyStruct1), sizeof(MyStruct2), sizeof(MyStruct3));

可以看到打印结果为 24-16-24

image

我们分析一下结构体中成员占用的空间,char占1个字节,double占8个字节,int占4个字节,short占2个字节。三个结构体都是相同的成员,只是排序不同,使得有些占用16个字节,有些则占用了24个字节,那是因为结构体的对齐方式是这样的:

1:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。

2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从 其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b, b 里有char, int, double等元素,那b应该从8的整数倍开始存储.)

3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大 成员的整数倍.不足的要补⻬。

上面这段文字如果很晦涩难懂的话,那接下来的图片应该会稍微容易理解
先以int距离,int占用4个字节,那么,他只能从下图的蓝色箭头位置为起始点进行存放
image
short占用2个字节,那么,他的存放起始点规则是这样的
image
同理,double只能这么存放
image
char占用1个字节,那每一个空间都可以成为他的起始点,图就不放了

回过头看上面的三个结构体,他们的内存占用应该如下:

Struct1
image
Struct2
image
Struct3
image

总结

1.OC中属性是以8字节对齐的,对象是以16字节对齐的

2. class_getInstanceSize 是获取对象实际占用的内存空间的值, 而 malloc_size 是获取系统为对象开辟的内存空间的值

3. 结构体的内存占用是根据内部成员来计算的,同样的内部成员排序不一样可能会占用不同的内存空间。结构体的字节对齐是以内部成员中最大的成员所占空间去对齐的,每个成员的起始位置只能在它所占空间的整数倍的位置进行存储