iOS底层原理探究——结构体内存对齐

315 阅读3分钟

提问:下面两个结构体打印结果是否一样?

struct CJStruct1 {
    double a;
    char b;
    int c;
    short d;
}struct1;

struct CJStruct2 {
    double a;
    int b;
    char c;
    short d;
}struct2;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

输出打印:

WeChat77396461aa9de653e84c0b6b2a170904.png

why?两个结构体只是内部的属性位置不一样,打印的大小也就不一样了?

先附上基本数据类型内存图

20210608141318406.png

内存对齐基本规则

  • 结构体struct或联合体union的数据成员第一个成员从offse为0开始,根据自己所规定的内存大小往后数多少位(如果第一个成员是结构体或者联合体就已该成员中最大内存字节往后数计算内存)后面的每个成员的存储起始位置为该成员或成员的子成员大小的整数倍数开始(比如int 是4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置mn)m=9n=4 9 10 11 12
  • 如果一个结构体有子结构体,那么该子结构体第一个内部元素存储起始位置要从该子结构体中最大的元素大小的倍数开始存储(struct a 有struct b b中有元素bool sub1,int sub2,double sub3。那么起始位置就应该是8的倍数)
  • 最后结构体的大小,sizeof()的大小必须是结构体中最大元素的大小的倍数,,不足补齐,向上取倍数。

由上分析可知:

struct CJStruct1 {
    double a;  // [0 8]   -> 0.1.2.3.4.5.6.7
    char b;    // [8 1]   -> 8
    int c;     // [12 4]  -> 12.13.14.15   //因为9.10.11不是4的倍数所以从12开始,占4个字节
    short d;   // [16 2]  -> 16.17         //结构体收尾补齐原则向上补齐8的倍数.得出结果是24
}struct1;

struct CJStruct2 {
    double a;   // [0 8]   -> 0.1.2.3.4.5.6.7
    int b;      // [8 4]   -> 8.9.10.11
    char c;     // [12 1]  -> 12
    short d;    // [14 2]  -> 14.15  //结构体收尾补齐原则向上补齐8的倍数.得出结果是16
}struct2;

//补充结构体嵌套结构体
struct CJStruct3{
    bool c;              // [0 1] -> 0
    double a;            // [8 8] -> 8.9.10.11.12.13.14.15
                                   //因为1.2.3.4.5.6.7不是4的倍数所有从12开始,占8个字节
    int b;              //  [16 4] -> 16.17.18.19
    struct CJStruct4{
          BOOL c;       //  [24 1] ->24
                                    //根据原则:子结构体起始值为该子结构体最大元素的倍数,所以(20.21.22.23)过滤
          double a;     //  [32 8] -> 32.33.34.35.36.37.38,39
                                    //因为25.26.27.28.29.30.31不是8的倍数所以从32开始,占8个字节
          char b;       //  [40 1] -> 40
      }struct4;         //结构体收尾补齐原则,所以应该41->48
    char d;             // [48 -1] 结构体收尾补齐原则 49补齐8的倍数 56
}struct3;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        NSLog(@"%lu-%lu-%lu",sizeof(struct1),sizeof(struct2),sizeof(struct3));
        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

2021-06-08 20:27:52.248607+0800 结构体内存对齐[33560:1718318] 24-16-56

那下面再看一段代码

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        CJPerson *p = [[CJPerson alloc]init];
        p.name = @"CJ";
        p.age = 18;
        p.sex = @"男";
        p.height = 175.5;
        NSLog(@"%@ \n sizeof打印:%lu\n class_getInstanceSize打印:%lu\n malloc_size打印:%lu",p,sizeof(p),class_getInstanceSize([CJPerson class]),malloc_size((__bridge const void *)(p)));

        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

WeChatdab2e78ab0db23c2c2ffbf0b2f2b1df6.png 总结:

  • sizeof其作用就是返回一个对象或者类型所占的内存字节数,后面可以是基本数据类型、对象、指针,而sizeof(p)其实就是计算指针的长度等于8
  • class_getInstanceSize 其本质是获取创建的对象至少所需的内存大小,8字节对齐。
  • malloc_size 计算对象实际分配的内存大小,也就是堆空间实际分配给对象的内存大小,并且是16字节对齐。