iOS alloc底层原理(2):内存分布

对象的本质其实就是结构体,(这点大家可以用clang命令查看编译后的代码加以验证,本文中就不多做论述了)。

要用到的命令:

  • clang -x objective-c -rewrite-objc -isysroot xxx.m//如果失败可以尝试下面的方法。
  • xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m//指定模拟器
  • xcrun -sdk iphoneos clang -rewrite-objc xxx.m//指定真机

对象内存影响因素

打印内存大小的三种方式

sizeof

  • sizeof:是一个运算符。
  • 用来获取类型(int、结构体、指针等)的大小
  • 这些数值在编译时生成,运行时直接获取。

class_getInstanceSize

  • 计算类最少需要的大小。
  • 一般以8字节对齐。
  • 使用是需要声明#import <objc/runtime.h>。

malloc_size

  • 堆空间实际分配给对象的内存大小。
  • 在Mac、iOS中总大小以16字节对齐。
  • 使用时需要声明#import <malloc/malloc.h>。

1、验证属性对内存占用的影响

创建一个继承于NSObject的JLObject类,

@interface JLObject : NSObject

@end
复制代码

记录其内存占用,实际使用8字节,开辟空间16字节。

截屏2021-06-09 下午10.36.18.png
添加一个属性name。

@interface JLObject : NSObject
@property (nonatomic, copy) NSString *name;
@end
复制代码

打印内存大小,发现实际使用的大小增加了8字节,开辟空间大小还是16字节。
截屏2021-06-09 下午4.04.54.png

2、验证成员变量对内存占用的影响

在NSObject中添加一个nickName成员变量

@interface JLObject : NSObject{
    NSString * nickName;
}
@property (nonatomic, copy) NSString *name;
@end
复制代码

增加一个字符串类型的ivar,内存占用多了8字节,所以成员变量会影响到对象的内存分布,实际使用24字节,超过了16字节,开辟空间变为48字节。

截屏2021-06-09 下午4.08.03.png

3、验证方法对内存占用的影响

接下来再为NSObject添加一个hello方法

@interface JLObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)hello;
@end

@implementation JLObject
- (void)hello{}
@end
复制代码

如图打印的结果依然的24,

所以添加方法并不会对对象的内存造成影响(实例方法存在于类里,类方法存在于元类中)。

截屏2021-06-09 上午11.19.07.png
属性=成员变量+setter、getter方法,由于方法不会影响,所以属性对对象内存的影响其实是ivar造成的。

结论

对象内存大小与方法无关,受ivars影响。

结构体与联合体(共用体)

各类型所占字节长度

signed char.png

结构体

结构体是由一批数据组合而成的一种新的数据类型。组成结构型数据的每个数据称为结构型数据的“成员”(引用于百度百科)。所占内存长度是各成员占的内存长度的总和。

结构体内存对齐原则

  • 1:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。
  • 2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)。
  • 3:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。

举个栗子:

struct JLObject {
    double a;       [0-7]//8字节,从0开始,放到[0-7]。
    char b;	    [8]//1字节,8可以整除1,放到[8]。
    int c;	    [12-15]//4字节,9不能整除4,找到距离最近可整除4的数为12,所以放到[12-15].
    short d;	    [16-17]//2字节,16可整除2,所以放到[16-17]
}object;            总大小:24//实际占用18个字节,总大小应该是最大成员的整数倍,所以此处为24

struct JLObject1 {
    double a;	        [0-7]//8字节,起始位置为0。所以放到[0-7]
    int b;        	[8-11]//4字节,8可整除,所以放到[8-11]
    char c;		[12]//1字节,放到[12]
    short d;		[14-15]//2字节,13不能整除2,下一个整倍数14,所以放到[14-15]
    int e;		[16-19]//4字节,16可以整除4,所以放到[16-19]
    struct JLObject obj; [24-47]//最大元素double8位,所以要从下一个8的倍数24开始,JLObject总大小是24,所以 结果是[24-47]
}object1; 		总大小:48//
复制代码

我们来打印验证一下:

截屏2021-06-09 下午2.29.55.png

内存优化

不同的排列占用内存大小也不同,系统对对象也有相应的优化,后面具体分析。

截屏2021-06-09 下午7.22.11.png

重排后内存占用由24缩减到16。
截屏2021-06-09 下午7.24.46.png

  • 由于内存对齐的影响,完全相同的类型组合,会因为排列不同,而影响到最终的内存占用。

联合体

在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。(百度百科)。
比如isa指针、runloop的source 都是联合体的实际应用。

联合体内存对齐原则:

  • 1、联合体的所有成员相对于基地在的偏移量都为0;
  • 2、宽度要大到足够容纳最宽的成员(一般为其最宽成员所占的大小);
  • 3、其对齐方式要适合于联合中所有类型的成员,size是所有成员类型长度最小公倍数,不满足则自动延伸;

例子:

union Un
{
    char a[10];//10字节
    int b;//4字节
    short c;//2字节
    double d;//8字节
};         
总大小:40字节。
分析:所有成员共用一片内存控件,偏移量都为0;
最宽成员char a[10]占用10字节,但是10不能被b和d整除,这样就不符合第三条规则,其大小应该是所有成员宽度的最小公倍数,也就是40
复制代码

对象内存分析

LLDB指令

po:输出值 或者 对象的地址
p/f(或e -f f --):输出浮点型的值
x/nuf <addr>
  x:读取内存的命令。
  n:表示要显示的内存单元的个数
  u:表示一个地址单元的长度:
    b(byte):表示单字节
    h(half word):表示双字节
    w(word):表示四字节
    g(giant word):表示八字节
  f:表示显示方式,可取如下值:
    x:十六进制格式
    d:十进制格式
    u:十进制格式无符号整型
    o:八进制格式
    t:二进制格式
    a:十六进制格式
    i:指令地址格式
    c:字符格式
    f:浮点数格式
复制代码

创建对象

//创建JLObject与其子类JLStudent,并为之添加占用不同内存长度的属性,如char、short、int、double、NSString等,用来验证不同类型在对象中的排列规则。

@interface JLObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic) double height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@interface JLStudent : JLObject
@property (nonatomic, copy) NSString *studentId;
@property (nonatomic) char c;
@property (nonatomic) short s;
@property (nonatomic) int  ranking;
@property (nonatomic) int  grade;
@end
复制代码

//创建对象并赋值

JLStudent *obj = [JLStudent alloc];
obj.name      = @"zjl";
obj.nickName  = @"好想秃顶";
obj.age       = 18;
obj.height    = 180.0;
obj.a         = 'a';
obj.b         = 'b';
obj.studentId = @"12345678";
obj.c         = 'c';
obj.ranking   = 1;
obj.grade     = 6;
obj.s         = 2;
复制代码

打印结果并分析

//打印obj内存

(lldb) x/10gx obj
0x600000944b40: 0x0000000106369b18 0x0000001200006261
0x600000944b50: 0x0000000106364038 0x0000000106364058
0x600000944b60: 0x0000000000000000 0x4066800000000000
0x600000944b70: 0x0000000100020063 0x0000000000000006
0x600000944b80: 0x0000000106364078 0x0000000000000000
复制代码

按顺序依次将地址所指向的内容打印出来

注意:
系统对内存进行了重新排列,以降低结构体对内存的占用;iOS打印内存为小端模式,所以需要倒过来看:

  • 0x0000001200006261实际包含了0x61、0x62、0x00000012。
  • 0x0000000100020063则需要拆分为0x63、0x2、0x00000001.

从打印结果我们可以发现,首先第一个位置是isa,然后存储都是父类JLObject中的数据,最后是JLStudent中的数据。

(lldb) po 0x0000000106369b18          JLObject //isa指针

//JLObject
(lldb) po 0x61                        97 //char:a = 'a'(参看ASCII码)
(lldb) po 0x62                        98//char:b = 'b'(参看ASCII码)
(lldb) po 0x00000012                  18 //int:age
(lldb) po 0x0000000106364038          zjl// NSString:name
(lldb) po 0x0000000106364058          好想秃顶//NSString:nickName
(lldb) e -f f -- 0x4066800000000000   (long) $13 = 180 //double:height,打印浮点数需要用到e -f f --或p/f

//JLStudent
(lldb) po 0x63                        99//char:c = 'c'(参看ASCII码)
(lldb) po 0x2                         2 //short:s
(lldb) po 0x00000001                  1 //int:ranking
(lldb) po 0x0000000000000006          6 //int:grade
(lldb) po 0x0000000106364078          12345678 //NSString:studentId
复制代码

结论:

  • 对象内存受isa与ivars影响。
  • 对象内存中的排列顺序依次为isa、各级父类的ivar、本类的ivar。
  • 排列时系统会对结构体内存进行优化重排(降低结构体占用内存的大小):
    • 类型先短后长,如上述打印按照char、short、int,NSString的优先级排列,
    • 相同长度的按照书写顺序排列,如父类JLObject中的name、nickName、height,

截屏2021-06-09 下午3.02.17.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享