2.OC底层-结构体内存对齐

207 阅读6分钟
  • 1.先了解各种数据类型所占用的字节数。 image.png

创建一个最基本的类

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end

我想知道这个类创建出来的对象需要多大的内存空间供它使用

  • 首先了解三个函数

  • sizeof :是一个运算符,获取的是类型的大小(int、size_t、结构体、指针变量等),这些数值在程序编译时就转成常数,程序运行时是直接获取的

  • class_getInstanceSize :是一个函数(调用时需要开辟额外的内存空间),程序运行时才获取,计算的是类的大小(至少需要的大小)

    • 创建的对象【至少】需要的内存大小
    • 不考虑malloc函数的话,内存对齐一般是以【8】对齐
    • #import <objc/runtime.h>
  • malloc_size :堆空间【实际】分配给对象的内存大小

    • 在Mac、iOS中的malloc函数分配的内存大小总是【16】的倍数
    • #import <malloc/malloc.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

LGPerson *p = [LGPerson alloc] ;
NSLog(@"int:%zd,string:%zd",sizeof(int),sizeof(NSString *));
NSLog(@"%zd",class_getInstanceSize([LGPerson class]));
NSLog(@"%zd",malloc_size(p));
NSLog(@"--------------------");

NSObject *p1 = [NSObject alloc] ;
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
NSLog(@"%zd",malloc_size(p1));

KCObjcBuild[1480:29429] <LGPerson: 0x10075b4a0>
KCObjcBuild[1480:29429] int:4,string:8
KCObjcBuild[1480:29429] 24
KCObjcBuild[1480:29429] 32
KCObjcBuild[1480:29429] --------------------
KCObjcBuild[1480:29429] 8
KCObjcBuild[1480:29429] 16

  • 结果是 int 4个字节 NSString* 8个字节,
    P至少需要的内存是24字节,实际开辟了32字节,
    P2至少需要的内存是8字节,实际开辟了16字节,
  • 所以思考为什么是24 而不是 20 , 为什么不是 24 而是 32,为什么是 8 为什么是 16?

NSObject 的内存分配

  • 属性、成员变量会影响对象的内存的大小,对象方法和类方法则不会。

  • 对象alloc 在堆空间中开辟了一段连续的内存空间,再被栈空间中的指针指向。

  • 这个是NSObject的最基本的内存分配下面我们在解释为什么是16。isa指针占用8字节 image.png

  • LGPerson对象的内存分配 image.png

  • 这里就有个疑惑这里只有20字节不是24。

LLDB指令

image.png

LLDB文档.png image.png

  • 这里我们发现并不是像我们上图所画的内存一致。而是age在name的前面而是这样的成为了24个字节(这个其实是编译器的优化)

image.png

  • 但是我发现上面的例子并不能说明中间有没有空位的所以改造一下

image.png

  • 多加一个height 属性

  • 打印发现还是24 还是 32 而 age和heigh放到了同一行中。

  • 浮点数打印 p/f 地址 image.png

image.png

对齐原则概述

  • 内存对齐的三大原则
    • 1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置m n) m = 9 n = 4 ( 9 10 11 12 )
    • 2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
    • 3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤成员的整数倍.不⾜的要补⻬。

举例讲解

struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17] 24
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15] 16
}struct2;

// 家庭作业 : 结构体内存对齐
struct LGStruct3 {
    double a;
    int b;
    char c;
    short d;
    int e;
    struct LGStruct1 str;
}struct3;
  • 1利用LGStruct1 分步骤讲解它的结构分配内存过程
    • 1.取出它最大的数据类型 为 8 所以以8 为倍数存 a -> [0,7]
      b -> [8] b为1放入 8 根据 第2个原则 8 是否为1的倍数,是就可以存入
      c -> [12,13,14,15] c 为 4 由于9 不是4的倍数,则省略后移舍弃,冲12开始存
      d -> [16,17] d 为2 16为2的倍数可以存,所以存入 16,17
    • 2.最后收尾要满足最大成员的整数倍 所以加到了24
  • 2 LGStruct2
    a -> [0,7]
    b -> [8,9,10,11]
    c -> [12]
    d -> [14,15]
    • 收尾加到16
  • 打印结果 NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));

image.png

  • 3 LGStruct3 一个结构体嵌套了LGStruct1的结构体

    • 1.取出它最大的数据类型 为 8 所以以8 为倍数存
      LGStruct3 a -> [0 7]
      LGStruct3 b -> [8 11]
      LGStruct3 c -> [12]
      LGStruct3 d -> [14,15]
      LGStruct3 e -> [16 19]


      LGStruct1 a -> [24 31]
      LGStruct1 b -> [32]
      LGStruct1 c -> [36 39]
      LGStruct1 d -> [40,41]

    • 2.最后收尾要满足最大成员的整数倍 所以加到了48

    • 3.也可以简化理解为 24+24 = 48

  • 为什么不能连续存要空出一些字节呢?

  • 因为内存读取为8个字节读取假如有一段这样的8字节内存数据计算机如何读取和优化。

image.png

  • 利用空间换取时间,编译速度加快。

内存对齐

  • 对象为结构体指针 (成员变量+isa)

  • 重新回到这个问题

    • 所以思考为什么是24 而不是 20 , 为什么不是 24 而是 32,为什么是 8 为什么是 16?
  • 现在我们已经知道为什么是 24 而不是 20 了 8+8+4 = 20 结构体对齐后为 24 8字节的结构体对齐。

  • 现在思考为什么又变成了 32 呢?

  • 上一个文章已经指出这里是绑定了isa给对象的,size则已经完成了基本的内存对齐的空间即24.

malloc_zone_calloc -> zone->calloc -> default_zone_calloc -> zone->calloc -> nano_calloc -> _nano_malloc_check_clear -> segregated_size_to_fit

image.png

  • 当无法找到calloc函数,po 它的存储值。发现真实调用的是 default_zone_calloc image.png
  • 通过汇编查找 image.png

image.png

k = (size + NANO_REGIME_QUANTA_SIZE(16) - 1) >> SHIFT_NANO_QUANTUM(4); // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size


k = ( size + 15 ) >> 4 << 4 移动四位 0001 0000 = 16 16字节对齐
即:40 / 16  = 2 --8 ->(2+1) * 16 = 48 

所以最后24 通过对象的内存 16字节对齐后 成为 32

  • 为什么是16 呢
  • OC对象自带有个isa 8字节 16是最小的基本单位。为了查找的快速,同样是利用空间换时间。

malloc_zone_calloc 流程图

malloc-image.png

结合上一篇文章 脑图

对象创建流程.png