美文网首页
iOS原理探索02-- 内存对齐

iOS原理探索02-- 内存对齐

作者: HardCabbage | 来源:发表于2020-09-14 17:45 被阅读0次

结构体内存对齐

我们首先定义两个结构体,分别计算他们的内存大小,并讨论内存对齐原理

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

// 内部需要的大小为: 15
// 最大属性 : 8
// 结构体整数倍: 24


// 15 -> 16

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;
// 15 -> 16

//打印两个结构体内存大小
NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));

打印结果

从两个结构体来看两者没啥大的区别,只是定义的属性顺序有些不一样,但结果相差很大,这是为什么呢?是否内存大小的计算存在一定的规则呢?下面就来讨论一下这两个结构体内存大小的不同的原因:内存对齐的规则

内存对齐的规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。在ios中,Xcode默认为#pragma pack(8),即8字节对齐

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

    我们通过上面的代码来计算一下stuct1和struct2的大小:

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

解释:从数据类型对用的自己数表格可以知道double占8字节、char占1字节、int占4字节、short占2字节,那么通过数据成员对齐原则,我们知道
a应该从0开始占8字节,那么a的内存范围[0-7];
b是char类型占1字节,那么b的内存范围[8];
c是int类型占4字节,那么c的内存范围本应该从9开始计算,但是9不是4的整数倍,那么我们往后累加+1,直到12是4的整数倍开始算占4位[12-15];
d是short类型占2字节,那么d的内存从16开始,16是2的整数倍,那么d的内存范围[16-17];
上面提到过内存对齐的三原则,按照结构体的总体大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐,17不是8的整数倍,不足要补齐,那么LGStruct1补齐后的内存大小为24。LGStruct2同理可得内存大小为16

结构体嵌套结构体

首先我们定义一个结构体嵌套结构体例子

struct LGStruct3 {
    double a;   // 0-7)
    char b;     // 1 [8 1] (8)
    int c;      // 4 [9 4] 9 10 11 (12 13 14 15)
    short d;    // 2 [16 2] (16 17)
    struct Mystruct{
        double e;       //8字节
        short f;         //1字节
    }Mystruct;
}struct3;

我们按照如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素的大小的整数倍地址开始存储。(struct a里存有struct b, b里有char,int,double等元素,那b应该从8的整数倍开始存储)原则来计算一下LGStruct3的内存大小,从上面的代码我们已经知道了LGStruct3内存分配完d时为[16-17],按照规则,Mystruct 应该从结构体内部的占位最大的字节整数倍开始算起,Mystruct 中的e为double占8字节,所以Mystruct 应该从18开始并且8的整数倍开始计算e的内存分配情况为[24-31]f占2字节[32-33],所以LGStruct3 实际占用的内存大小为33,但是总大小要为其内部最大元素的大小整数倍即>33&&是8的整数倍40`。

内存优化

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

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

如果是结构体中数据成员是根据内存从大到小的顺序定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding即可满足堆存对齐规则,这种方式就是苹果中采用的,利用空间换时间,将类中的属性进行重排,来达到优化内存的目的
下面案例具体说明以上结论:
自定义一个类,并为这个类添加一些属性,我们通过断点调试,来查看属性的内存地址分配情况


@interface LGPerson : 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



int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // 内存对齐

        LGPerson *person = [LGPerson alloc];
        person.name      = @"Cooci";
       person.nickName  = @"KC";
       person.age       = 18;
       person.c1        = 'a';
       person.c2        = 'b';


        NSLog(@"%@",person);
        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
lldb调试结果
  • 通过地址找出 name & nickName,控制台有打印结果;
  • 通过地址查找person.age、person.c1、person.c2却显示的是乱码,这个是为什么呢?我们猜测这个是苹果进行了内存重排优化,下面我们来验证一下这个猜想;当我们向通过0x0000001200006261地址找出age等数据时,发现是乱码,这里无法找出值的原因是苹果中针对age、c1、c2属性的内存进行了重排,因为age类型占4个字节c1和c2类型char分别占1个字节,通过4+1+1的方式,按照8字节对齐,不足补齐的方式存储在同一块内存中。我们按照这个思路来打印一下结果
  • age的读取通过0x00000012 0x00000012
  • c1的读取通过0x62 0x62
  • c2的读取通过0x61 0x61

总结:大部分的内存还是按照顺序来分配的,苹果除了使用内存对齐规则外,会针对属性自行重排。

相关文章

  • iOS内存对齐

    这篇文章我们来探索一下iOS内存对齐的原理,在探索完内存对齐原理之后,你就会明白内存对齐的好处。 在讲述内存对齐时...

  • iOS原理探索02-- 内存对齐

    结构体内存对齐 我们首先定义两个结构体,分别计算他们的内存大小,并讨论内存对齐原理 从两个结构体来看两者没啥大的区...

  • iOS底层探索-目录

    iOS底层探索001-alloc&init&new源码分析 iOS底层探索002-内存对齐 iOS底层探索003-...

  • iOS底层探究 - 内存对齐

    目录1:内存对齐的原因2:内存对齐的规则3:结构体内存分配演练以及在iOS中对象成员的内存分配探索 一 :内存对齐...

  • iOS底层原理探索—内存原理对齐

    获取内存大小的3种方式 sizeof class_getInstanceSize malloc_size size...

  • iOS探索 多线程之GCD应用

    摘于文章:链接: 欢迎阅读iOS探索系列(按序阅读食用效果更加)iOS探索 alloc流程iOS探索 内存对齐&m...

  • iOS 内存对齐探索

    什么是内存对齐 内存对齐是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方...

  • iOS-底层原理 05:内存对齐原理

    iOS 底层原理 文章汇总 在探讨内存对齐原理之前,首先介绍下iOS中获取内存大小的三种方式 获取内存大小的三种方...

  • iOS 内存对齐原理

    计算机中内存空间是按照 byte 划分的,计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首...

  • iOS 内存对齐原理

    获取内存大小 第一种:sizeof The sizeof keyword gives the amount of ...

网友评论

      本文标题:iOS原理探索02-- 内存对齐

      本文链接:https://www.haomeiwen.com/subject/ujmvektx.html