美文网首页
结构体的内存对齐

结构体的内存对齐

作者: Bel李玉 | 来源:发表于2020-09-12 00:38 被阅读0次

    在计算机系统中,为方便,快速的从内存中读取内存值,要求内存中的变量值需要按照一定的规则进行排放,在这篇文章中我们一起讨论结构体中的内存对齐。

    内存对齐原则:

    • 1:数据成员对齐原则结构体(struct)联合体(union)的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员存储的起始位置从该成员大小整数倍开始。
    • 2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小整数倍地址开始存储。
    • 3:收尾工作: 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员整数倍,不足的要补齐。

    分析:

    单个结构体

    我们通过探讨下面两个结构体的内存分布,来探讨该问题

    //1、定义两个结构体
    struct Mystruct1{
        char a;     //1字节
        double b;   //8字节
        int c;      //4字节
        short d;    //2字节
    }Mystruct1;
    
    ![内存对齐2.png](https://img.haomeiwen.com/i1891462/460d8737b79e80e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    //计算 结构体占用的内存大小
    NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));
    

    我们假设从内存为 0的位置开始存储

    对于MyStruct1而言:

    • 1,char a: achar类型,占用 1字节位置 0存放 char a
    • 2,double b: bdouble类型,占用 8字节 ,根据 原则1,要从 8 的整数倍位置处存放b,则在 位置8 存放 double b的值。到目前为止,0~7存放的是 char a8 ~ 15存放的是double b
    • 3,int c : cint 类型,占用 4字节,根据 原则1,需要存放在 4的整数倍的位置,则 从 位置16开始存放。16~20存放 int c。此时 ,0~7存放的是 char a的值,8~15存放的是double b的值,16~19存放的是int c的值。
    • 4,short d: dshort类型,占用 2字节,应该从2的整数倍位置开始存储。则在 20~21 位置处存放 short d的值。

    MyStruct1结构体的内存分布如下图所示,白色区域为填充区域,有颜色的地方为存放变量的区域。根据原则3结构体的大小为最大成员变量的整数倍原则,我们可以看出,其大小为 24.

    内存对齐1.png

    对于Mystruct2而言:

    • 1,double b为第一个成员,从 0位置 开始存储,占用 8字节,则 0~7 存放 double b的值。
    • 2,int c需要占用 4字节,需要从 4的整数倍开始存储,则 4~7存放 int c的值。
    • 3,short d需要占用 2字节,从2的整数倍位置开始存储,则 8~9 存放 short d的值。
    • 4,char a需要占用 1字节,从1 的整数倍位置开始存储,则 位置10 存放 char a的值。

    下图为Mystruct2的内存分布,我们可以看出 Mystruct2的内存大小为 16

    内存对齐2.png

    ⚠️⚠️⚠️我们通过上面两个例子,来讲解结构体的内存对齐原理,我们可以看出,当结构体的成员一样时,其占用的内存大小并不一致,其内存大小与成员的排列顺序有关系

    结构体嵌套

    为了分析结构体嵌套里面的内存分布,我们定义如下

    struct Mystruct2{
        double b;   //8字节
        int c;      //4字节
        short d;    //2字节
        char a;     //1字节
    }Mystruct2;
    
    struct MyStruct3{
        char a;     //1字节
        double b;   //8字节
        struct Mystruct2 struc; // 16 字节
        int c;      //4字节
        short d;    //2字节
    
    }MyStruct3;
    

    MyStruct3中嵌套了 Mystruct2

    • 1,根据前面单个结构体里面阐述的char a 占用 0 ~ 7字节空间,double b 占用 8 ~ 15字节空间。
    • 2, 根据原则2Mystruct2结构体里面最大的成员变量8字节,所以,要从8的整数倍的位置处开始存放 Mystruct2168的整数倍,所以,16 ~ 31存放 Mystruct2
    • 3,int c,需占用4字节,需要从4的整数倍位置处存放,则 32 ~ 35处存放 int c
    • 4,short d,需要占用 2字节,从 2的整数倍位置处开始存放,则36 ~ 37存放 short d

    根据以上分析,我们可以得到,MyStruct3占用了 0~37位置的空间,根据 原则3,它所占总空间应该为 其内部最大成员大小的整数倍MyStruct3的最大成员大小为 8,所以,其占用内存空间为 0 ~ 39,共 40字节空间。其内存分布如下图所示:

    内存对齐3.png

    内存优化(属性重排)

    在我们讲解第一个示例的时候,我们可以看出,结构体内存的大小和成员变量的大小和其排放顺序有关,两个结构体如果拥有同样的属性,但其属性排放顺序不一致,会导致占用内存的大小不一致。原因如下:

    • 如果结构体内存成员变量是从小到大进行排布的,则需要更多的padding会导致占用的内存偏大。
    • 如果结构体内,成员变量是从大到小进行排布,则会有很少的padding,会使结构体占有更少的空间。
      在 iOS中,苹果会对进行属性重排,来达到优化内存的目的,我们以下面的代码为例
    @interface LYPerson : NSObject
    
    @property(nonatomic, copy) NSString *name;
    @property(nonatomic, assign) char a;
    @property(nonatomic, assign) int c;
    @property(nonatomic, assign) short d;
    @end
    
    @implementation LYPerson
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LYPerson *person = [LYPerson alloc];
            person.c  = 5;
            person.a = 'a';
            person.name = @"LY";
            person.d = 2;
            NSLog(@"%ld", class_getInstanceSize(person.class));
    
        }
        return 0;
    }
    

    首先,我们来看下输出结果:

    2020-09-12 13:24:17.099525+0800 KCObjc[59477:1943607] 24
    

    为什么是24 ???

    我们可以通过lldb指令查看一下内存分布情况

    (lldb) po person                              \\  1
    <LYPerson: 0x1012492c0>
    (lldb) memory read/8gx 0x1012492c0 \\ 2
    0x1012492c0: 0x001d80010000238d 0x0000000500020061
    0x1012492d0: 0x0000000100001010 0x0000000000000000
    0x1012492e0: 0x0000000000000000 0xbd2293c8794d4e12
    0x1012492f0: 0x00000001003f4000 0x0000000000000001
    
    (lldb) x/8gx 0x1012492c0            \\ 3
    0x1012492c0: 0x001d80010000238d 0x0000000500020061
    0x1012492d0: 0x0000000100001010 0x0000000000000000
    0x1012492e0: 0x0000000000000000 0xbd2293c8794d4e12
    0x1012492f0: 0x00000001003f4000 0x0000000000000001
    
    • 1,打印 person对象,得到 person对象的内存地址。
    • 2,读取0x1012492c0内存区域的存储值, memory read/8gx 地址指令用来读取内存值,
    • 3, x/8gx 0x1012492c0xmemory read/的缩写行式。8,是输出的数量个数,ggiant word 8字节,最后一个x表示以16进制格式输出
    (lldb) po 0x001d80010000238d & 0x00007ffffffffff8ULL // 1
    LYPerson 
    
    (lldb) po 0x0000000100001010 // 2
    LY
    
    (lldb) po 0x0005 //3
    5
    
    (lldb) po 0x0002 // 4
    2
    
    (lldb) po 0x0061 //5 
    97
    
    • 1,在该内存的前8个字节存储的是 对象的 isa值,0x00007ffffffffff8ULLISA_MASK
    • 2,从 16 ~ 23字节处,储存的是对象的name属性。
    • 3, 10 ~ 11字节处,储存的是 属性c的值。
    • 4,12 ~ 13 字节处,存储的是属性d 的值。
    • 5, 14 ~ 15字节处,存储的是属性a的值, 97字母aASCII值。

    其内存分布如下所示:


    LYPerson内存分布.png

    总结:

    我们在这里首先讨论了,内存对齐的三原则单个结构体的内存分布结构体嵌套的内存分布,以及 iOS中的内存对象的属性重排和其内存分布

    相关文章

      网友评论

          本文标题:结构体的内存对齐

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