首页 > 其他 > 详细

002:对象原理中-内存对齐

时间:2020-11-30 09:26:31      阅读:41      评论:0      收藏:0      [点我收藏+]

问题

目录

预备知识

1:lldb调试指令

po               打印信息
p                打印详细的信息
bt               打出堆
register read    读取寄存器
x                读取内存段
x/4gx            读取4段内存段

2:计算内存大小的三种方法

     LWPerson * p = [LWPerson alloc];
     LWPerson * q;
        
     NSLog(@"对象类型占用内存大小--%lu",sizeof(p));
     NSLog(@"对象类型占用内存大小--%lu",sizeof(q));
     NSLog(@"对象实际内存大小-----%lu",class_getInstanceSize([p class]));
     NSLog(@"对象实际内存大小-----%lu",class_getInstanceSize([q class]));
     NSLog(@"系统为分配的内存大小--%lu",malloc_size((__bridge const void *)(p)));
     NSLog(@"系统为分配的内存大小--%lu",malloc_size((__bridge const void *)(q)));

2.2:打印结果如下

     2020-09-19 22:59:42.507118+0800 KCObjcTest[4003:298596] 对象类型占用内存大小--8
     2020-09-19 22:59:42.507678+0800 KCObjcTest[4003:298596] 对象类型占用内存大小--8
     2020-09-19 22:59:42.507780+0800 KCObjcTest[4003:298596] 对象实际内存大小-----8
     2020-09-19 22:59:42.507844+0800 KCObjcTest[4003:298596] 对象实际内存大小-----0
     2020-09-19 22:59:42.507910+0800 KCObjcTest[4003:298596] 系统分配的内存大小--16
     2020-09-19 22:59:42.507963+0800 KCObjcTest[4003:298596] 系统分配的内存大小--0

2.3:总结

sizeof传进来的是类型,用来计算这个类型占多大内存,这个在编译器编译阶段就会确定,所以sizeof(p)sizeof(q)的结果都是一样的,pq都是指针类型,指针大小就是8个字节

class_getInstanceSize对象的实际内存大小,大小由类的属性和变量来决定,实际上并不是严格意义上的对象内存大小,因为底层进行8字节对齐算法define WORD_MASK 7UL((x + WORD_MASK) & ~WORD_MASK,LWPerson类中没有其他的属性和变量,但是继承了NSObjectNSObject中有一个isa指针,所以内存大小是8字节

malloc_size系统分配的内存大小是按16字节对齐的方式,即是按16的倍数分配 ,不足则系统会自动填充字节(具体的calloc详细流程后续会更新

3:各类型所占字节

技术分享图片

正文-内存对齐

1:为什么要内存对齐

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

3、以空间换时间 

参考: 为什么要进行内存对齐?

2:内存对齐规则

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

数据成员的对齐规则可以理解为min(m, n)的公式, 其中 m表示当前成员的开始位置n表示当前成员所需要的位数。如果满足条件 m 整除 n(即 m % n == 0), n从 m位置开始存储, 反之继续检查 m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。

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

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

3:单个结构体内存对齐

技术分享图片

3.1:结构体MyStruct1 内存大小计算

根据内存对齐规则计算MyStruct1的内存大小,详解过程如下:

  • 变量a:占1个字节,从0开始,此时min(0,1),即 0 存储 a
  • 变量b:占8个字节,从1开始,此时min(1,8),1不能整除8,继续往后移动,知道min(8,8),从8开始,即 8-15 存储 b
  • 变量c:占4个字节,从16开始,此时min(16,4),16可以整除4,即 16-19 存储 c
  • 变量d:占2个字节,从20开始,此时min(20, 2),20可以整除2,即20-21 存储 d

因此MyStruct1的需要的内存大小为 15字节,而MyStruct1中最大变量的字节数为8,所以 MyStruct1 实际的内存大小必须是 8 的整数倍,18向上取整到24,主要是因为24是8的整数倍,所以 sizeof(MyStruct1) 的结果是 24

3.2:结构体MyStruct2 内存大小计算

根据内存对齐规则计算MyStruct2的内存大小,详解过程如下:

  • 变量b:占8个字节,从0开始,此时min(0,8),即 0-7 存储 b
  • 变量c:占4个字节,从8开始,此时min(8,4),8可以整除4,即 8-11 存储 c
  • 变量d:占2个字节,从12开始,此时min(12, 2),12可以整除2,即12-13 存储 d
  • 变量a:占1个字节,从14开始,此时min(14,1),即 14 存储 a

因此MyStruct2的需要的内存大小为 15字节,而MyStruct1中最大变量的字节数为8,所以 MyStruct2 实际的内存大小必须是 8 的整数倍,15向上取整到16,主要是因为16是8的整数倍,所以 sizeof(MyStruct2) 的结果是 16、

引用:单个结构体内存大小

4:结构体嵌套结构体

结构体中嵌套结构体的内存大小计算情况

//1、结构体嵌套结构体
struct Mystruct3{
    double b;   //8字节
    int c;      //4字节
    short d;    //2字节
    char a;     //1字节
    struct Mystruct2 str; 
}Mystruct3;

//2、打印 Mystruct3 的内存大小
NSLog(@"Mystruct3内存大小:%lu", sizeof(Mystruct3));
NSLog(@"Mystruct3中结构体成员内存大小:%lu", sizeof(Mystruct3.str));

技术分享图片 

分析Mystruct3的内存计算

  • 根据内存对齐规则,来一步一步分析Mystruct3内存大小的计算过程

    • 变量b:占8个字节,从0开始,此时min(0,8),即 0-7 存储 b
    • 变量c:占4个字节,从8开始,此时min(8,4),8可以整除4,即 8-11 存储 c
    • 变量d:占2个字节,从12开始,此时min(12, 2),20可以整除2,即12-13 存储 d
    • 变量a:占1个字节,从14开始,此时min(14,1),即 14 存储 a
    • 结构体成员str:str是一个结构体,根据内存对齐原则二结构体成员要从其内部最大成员大小的整数倍开始存储,而MyStruct2最大的成员大小为8,所以str要从8的整数倍开始,当前是从15开始,所以不符合要求,需要往后移动到16,16是8的整数倍,符合内存对齐原则,所以 16-31 存储 str

因此MyStruct3的需要的内存大小为 32字节,而MyStruct3中最大变量为str, 其最大成员内存字节数为8,根据内存对齐原则,所以 MyStruct3 实际的内存大小必须是 8 的整数倍,32正好是8的整数倍,所以 sizeof(MyStruct3) 的结果是 32

技术分享图片

5: 内存优化

结构体内存大小与结构体成员内存大小的顺序有关

MyStruct1 通过内存字节对齐原则,增加了9个字节,而MyStruct2通过内存字节对齐原则,通过4+2+1的组合,只需要补齐一个字节即可满足字节对齐规则,这里得出一个结论结构体内存大小与结构体成员内存大小的顺序有关

  • 如果是结构体中数据成员是根据内存从小到大的顺序定义的,根据内存对齐规则来计算结构体内存大小,需要增加有较大的内存padding即内存占位符,才能满足内存对齐规则,比较浪费内存

  • 如果是结构体中数据成员是根据内存从大到小的顺序定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding即可满足堆存对齐规则,这种方式就是苹果中采用的,利用空间换时间,将类中的属性进行重排,来达到优化内存的目的

苹果中属性重排,即内存优化

6:属性重排

1:定义一个自定义CJLPerson类,并定义几个属性

@interface CJLPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@implementation CJLPerson

@end

2:在main中创建CJLPerson的实例对象,并对其中的几个属性赋值

int main(int argc, char * argv[]) {
    @autoreleasepool {
        CJLPerson *person = [CJLPerson alloc];
        person.name      = @"CJL";
        person.nickName  = @"C";
        person.age       = 18;
        person.c1        = a;
        person.c2        = b;

        NSLog(@"%@",person);
    }
    return 0;
}

3:断点调试

技术分享图片

当我们向通过0x0000001200006261地址找出age等数据时,发现是乱码,这里无法找出值的原因是苹果中针对age、c1、c2属性的内存进行了重排,因为age类型占4个字节,c1和c2类型char分别占1个字节,通过4+1+1的方式,按照8字节对齐,不足补齐的方式存储在同一块内存中,

  • age的读取通过0x00000012
  • c1的读取通过0x61(a的ASCII码是97)
  • c2的读取通过0x62(b的ASCII码是98)
技术分享图片

 

4:CJLPerson的内存分布情况

技术分享图片

 

注意:
1、char类型的数据读取出来是以ASCII码的形式显示
2、图片中地址为0x0000000000000000,表示person中还有属性未赋值

这里可以总结下苹果中的内存对齐思想:

  • 大部分的内存都是通过固定的内存块进行读取,
  • 尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费的,苹果会自动对属性进行重排,以此来优化内存

注意

 

引用

1:为什么要内存对齐

 

002:对象原理中-内存对齐

原文:https://www.cnblogs.com/zyzmlc/p/14059114.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!