结构体内存对齐解析

531 阅读3分钟

在我们实际开发过程中,经常需要对各种类型(类、对象、结构体等)开辟内存,不同类型如何在内存中存储数据,以及需要开辟多少内存?本篇文章主要对结构体类型内存分配做相关的分析。

结构体内存对齐三大原则:

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

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

3. 结构体的总大小:也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

下面是各个类型所占内存大小的表格

内存占比.png

案例分析

根据内存对齐原则的解释,下面我们通过例子来分析

// []中表示分配的位置,()表示空,不存储
struct S1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11) [12 13 14 15]
    short d;        // 2    [16 17]
} struct1;
// 总大小 24

从struct1来看,double a占8字节,从0字节开始位置[0 7],char b占1字节,从位置8开始[8],int c占4字节,从位置9开始,因为9不是4的倍数,所以9-11空着,c的位置是[12 15],short d占2字节,从位置16开始,16也是2的倍数,d的位置是[16 17],根据结构体总大小的定义,必须是内部最大成员的整数倍,最大成员是8,18需要补齐,所以总大小为24字节

以上是通过表格对应的各种类型根据结构体内存对齐原则手动计算得出,接下来我们实际代码运行验证一下是不是和计算的一样。

// 在main函数里打印一下
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        NSLog(@"s1.size = %lu", sizeof(struct1));
        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

11.png 可以看到实际输出和我们计算是一致的,总大小没问题,那实际内部是存储每个元素是不是和我们算的一致,还是通过实际运行代码查看结果,先看下面的内存分析图

22.png

根据上图分析,可以输出每个元素对应的地址来查看 00.png 可以看到a的地址是0x10257b810,b的地址刚好是a+8字节=0x10257b818,c的地址刚好是b+4字节,d的地址是c+4字节。

结构体嵌套结构体

结构体嵌套只要保证对齐原则,实际操作也是类似的,以下面为例

struct S2 {
    double a;     // 8 [0 7]
    int b;        // 4 [8 11]
    char c;       // 1 [12]
    short d;      // 2 (13) [14 15]
    int e;        // 4 [16 19]
    struct S1 s1; // 24 (20 21 22 23) [24 47] 48
} struct2;

根据对齐原则第二条,结构体成员要从其内部最大元素大小的整数倍地址开始存储,S1中最大的元素是8,所以上面20后面要到24开始存储,最终结构S2的大小是48。

s11.png

55.png 通过图片的分析和实际运行的结果得出和我们之前的计算是一样的。

总结

结构体内存的对齐原则,可以提高计算机访问效率,降低访问的出错率。