iOS 结构体内存对齐探究

417 阅读4分钟

一、为什么要内存对齐

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

2、 性能原因:计算机访问内存并不是以1个字节为单位的,而是以2、4、8、16、32个字节为单位的。经过内存对齐后,CPU的内存访问速度大大提升。(参考计算内存访问粒度

二、结构体内存对齐规则

首先明确2个概念:

1、对齐系数,影响对齐的边界条件,由编译预定义,可通过预处理命令#pragma pack(n)来更改,n就是对齐系数。在Xcode编译环境下,32、64的对齐系数分别是4、8.
2、以下说的结构体成员的位置都是该成员相对于结构体首地址的偏移位置

规则:

1、第一个成员放在位置为0的地方
2、对于非第一个的其他成员,先算出对齐系数和该成员数据类型长度中的较小值,然后该成员的位置要是该较小值的整数倍。
3、结构体的长度是成员最长长度和对齐系数的较小值的整数倍

三、小试牛刀

环境: Xcode12.4,iOS14.4

例一:

struct LGStruct1 {
    double a;//8 0-7
    char b;//1 8
    int c;//4 12-15
    short d;//2 16-17
} struct1;//24

struct LGStruct3 {
    double a; //8 0-7
    int b;  //4 8-11
    char c; //1 12
    short d; //2 14
    int e;  //4 16-19
    struct LGStruct1 str;//24 24 - 47
} struct3;//48

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"struct1 addr:%p size:%lu",&struct1,sizeof(struct1));
    NSLog(@"struct3 addr:%p size:%lu", &struct3,sizeof(struct3));
    
}
@end
2021-06-09 09:27:38.389804+0800 StructMemAlignDemo[2682:55621] struct1 addr:0x10e8526e8 size:24
2021-06-09 09:27:38.389895+0800 StructMemAlignDemo[2682:55621] struct3 addr:0x10e852700 size:48

上例中的struct3中各基本数据类型成员的对齐都好理解,关键是结构体成员LGStruct1 str的对齐规则,str的长度是24,那么是按str的长度24来对齐的么?此处str的位置正好是24,是巧合么?我们接下来验证一下:

例二:

struct LGStruct1 {
    double a;//8 0-7
    char b;//1 8
    int c;//4 12-15
    short d;//2 16-17
    double e;//8 24 - 31
} struct1;//32

struct LGStruct3 {
    double a; //8 0-7
    int b;  //4 8-11
    char c; //1 12
    short d; //2 14
    int e;  //4 16-19
    struct LGStruct1 str;//32 32-63
} struct3;//64
2021-06-09 09:29:17.386293+0800 StructMemAlignDemo[2732:57449] struct1 addr:0x103f496e8 size:32
2021-06-09 09:29:17.386394+0800 StructMemAlignDemo[2732:57449] struct3 addr:0x103f49708 size:56

可是控制台输出的是56,为什么不是按结构体str的长度32来确定其位置呢,请回看对齐规则的第二条:“成员对齐时的位置由对齐系数和成员长度中的较小值决定”,当前环境下的系数为8,所以str的位置是8的倍数就可以了,故str的位置为24。当对齐系数小于成员的长度时,对齐系数就会影响成员的对齐。纠正如下:

struct LGStruct1 {
    double a;//8 0-7
    char b;//1 8
    int c;//4 12-15
    short d;//2 16-17
    double e;//8 24 - 31
} struct1;//32

struct LGStruct3 {
    double a; //8 0-7
    int b;  //4 8-11
    char c; //1 12
    short d; //2 14
    int e;  //4 16-19
    struct LGStruct1 str;//24 24-55
} struct3;//56

例三:

再来一个结构体成员长度小于对齐系数的例子:

struct LGStruct4 {
    char a;//1 0
    char b;//1 1
    char c;//1 2
} struct4;//3

struct LGStruct5 {
    double a; //8 0-7
    int b;  //4 8-11
    char c; //1 12
    short d; //2 14
    int e;  //4 16-19
    struct LGStruct4 str;//3 21-23
} struct5;//24
2021-06-09 09:53:23.901418+0800 StructMemAlignDemo[3281:72420] struct4 addr:0x10130a780 size:3
2021-06-09 09:53:23.901499+0800 StructMemAlignDemo[3281:72420] struct5 addr:0x10130a788 size:24

总结:

对齐系数非常重要,挖坑小帮手。 我觉得网上其他大佬说的结构体的长度是成员最长长度的整数倍是片面的。如果说结构体的长度是基础数据类型成员最长长度的整数倍,也还可以,但没有排出对齐系数的影响。 还有结构体成员会直接在上一层结构体中展开;领会对齐系数的影响。

补充:

上述研究的是结构体内部的成员对齐,在实际应用过程中还要注意:在iOS系统中, malloc、calloc申请的内存的是按16字节对齐的。