OC对象的内存分配及内存对齐

2021-07-25  本文已影响0人  希尔罗斯沃德_董

对象是如何分配内存的?对象是如何计算内存大小的呢?对象内存分配跟什么有关?

代码分析

sizeof() 计算一个变量或者类型的大小(以字节为单位)
class_getInstanceSize 计算对象所需要的内存大小,结算结果遵循8字节对齐,其实现源码如下:

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

malloc_size 计算对象的实际大小,使用时需要引入头文件

//没有添加任何属性
@interface LNPerson : NSObject
@end
@implementation LNPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      Person *person = [LNPerson alloc];
      NSLog(@"%lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
    }
    return 0;
}

打印结果:8 - 8 - 16

//添加一个属性
@interface LNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LNPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LNPerson *person = [LNPerson alloc];
        NSLog(@"打印结果: %lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
    }
    return 0;
}

打印结果: 8 - 16 - 16

//添加两个属性
@interface LNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end

@implementation LNPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LNPerson *person = [LNPerson alloc];
        NSLog(@"打印结果: %lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
    }
    return 0;
}

打印结果: 8 - 24 - 32
这里先附上一张数据类型占用内存大小的表格:

图1.1 数据类型占用内存大小.png

由以上三分代码运行结果可以知道对象的内存大小跟属性有关(实际上是跟实例变量有关)。代码分析如下:

什么是内存对齐

内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。内存对齐意味将数据类型写入到内存地址时是按照它们大小切割的。简单说就是如果内存地址是n字节的倍数,那么我们说这n字节是内存对齐的,注意,这里n是2的幂,说白了,内存地址正好放下n字节的倍数,两者相除余数为零,正好整除。例如,从上面的代码分析可以看出对象person所需要的实际大小为24字节,而实际大小确是32字节,这是因为OC对象的内存是16字节对齐的。

为什么需要内存对齐

那编译器为什么要进行内存对齐呢?主要基于以下两个原因:

内存对齐遵循什么样的原则

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要成员有子成员,比如说数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。min(当前开始的位置m n))m = 9, n= 4:9 10 11 12 ,12是4的整数倍,所以从12开始存储
2、结构体作为成员:如果一个结构体里有些结构体成员,则结构体成员要从其内部最大元素的整数倍地址开始存储。(struct a里面存储struct b, b里有char、int、 double等元素,那b应该从8的整数倍开始存储。)
3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
举个例子:

// 64位
struct MyStruct1 {
    double a;//8字节 a存储为0~7位置共8位
    char b;//1字节 8是1的整数倍,所以b从8位置开始存储,共1位
    int c;// 4字节 前8位已被占用,9不是4的倍数,所以得往前找最近的4的倍数12,从12位开始存储,共四位12~15
    short d;//2字节 16位刚好是2的倍数,从16位开始存储,共两位16~17,可以推出struct1的所需大小为0~17共18位
}struct1;// 但是结构大小是其内部最大成员(这里的a,8字节)的整数倍,因此最小为24

struct MyStruct2 {// 同上面的推理
    double a;//8 0~7
    int c;// 4 8是4的倍数 8~11
    char b;//1 12
    short d;//2 13不是2的整数倍, 故14~15,所需大小位0~15共16位,实际大小应该为16
}struct2;

struct MyStruct3 {// 同上面的推理
    double a;//8 0~7
    int c;// 4 8是4的倍数 8~11
    char b;//1 12
    short d;//2 13不是2的整数倍, 故14~15,所需大小15
//如果一个结构体里有些结构体成员,则结构体成员要从其内部最大元素的整数倍地址开始存储。所以虽然这里myStruct2的大小为16字节,但是依然以8字节为倍数开始计算,这里16刚好是8的整数倍,所以myStruct2从16位开始,大小16位,所以应该是16~31位,总共大小32位
    struct MyStruct2 myStruct2;//16
}struct3;// 32

打印这三个结构体:

    NSLog(@"struct1大小:%ld",sizeof(struct1));
    NSLog(@"struct2大小:%ld",sizeof(struct2));
    NSLog(@"struct3大小:%ld",sizeof(struct3));

打印结果:

2021-07-25 17:12:57.383194+0800 MemoryAlignmentTest[8346:636413] struct1大小:24
2021-07-25 17:12:57.383265+0800 MemoryAlignmentTest[8346:636413] struct2大小:16
2021-07-25 17:12:57.383295+0800 MemoryAlignmentTest[8346:636413] struct3大小:32

结果是符合规则的。

备注:OC对象的底层结构是结构体,结构体大小内存分配遵循8字节对齐原则。但是OC对象在结构8字节对齐计算出的内存大小的基础上遵循16字节对齐原则。这么做应该是出于兼容性、容错的等方面的考虑,给对象留点空间,避免挤满。

上一篇下一篇

猜你喜欢

热点阅读