结构体内存对齐

2021-06-09  本文已影响0人  Nulll

前言

OC语言底层是基于cc++的,而NSObject在底层也是用结构体实现的,所以了解了结构体的内存对齐问题对于理解OC底层的内存分配问题就显得至关重要。


先看一个 arm64下面各种数据类型占用的内存大小
在开始之前先看一看arm64下面个数据类型所占用空间大小

数据占用内存大小

结构体内存对齐

1、最简单的无嵌套结构体

struct LGStruct1 {
    double a;       // 8    
    char b;         // 1     
    int c;          // 4     
    short d;        // 2    
}struct1;

struct LGStruct2 {
    double a;       // 8    
    int b;          // 4     
    char c;         // 1    
    short d;        // 2    
}struct2;

 NSLog(@"---%lu ---%lu",sizeof(struct1),sizeof(struct2));

上面这两个结构体数据类型和个数一模一样,但是这两个结构体需要开辟的内存是否一样呢?那我们用实际的出结论。


无嵌套.png

从上面的结论可以看到,这两个结构体所需要的空间并不是(1 + 2 + 4 + 8 == 15);而且所开辟的内存空间也是不一样的;就很奇怪。

结构体的内存对齐问题;下面一段摘自LG-Cooci

  1. 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置mn)m=9n=4 9 10 11 12
  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
  3. 收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。

根据上面这个规则开辟的对象内存如下面这样,
LGStruct1 的内存位置如下图所示

LGStruct1.png

LGStruct2 如下

LGStruct2.png
struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17]         --8字节对齐: 24
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15]     --8字节对齐: 16
}struct2;

2、稍微复杂的-带有嵌套。

情况一:结构体里面嵌套结构体

struct LGStruct3 {
    double a;               //8     [0  7]
    int b;                  //4     [8 9 10 11]
    char c;                 //1     [12]
    short d;                //2     (13 [14 15]
    int e;                  //4     [16 17 18 19]
    struct LGStruct1 str;   //实际占用18字节    (20 ... 23) [24... 41]  --8字节对齐为: 48
}struct3;

//a 在第一位:占用的位置信息为 0 - 7 共八个字节
//b 4个字节:且8位4的倍数,所以占用 8,9,10,11公4个字节
//c 占用一个字节,12 位
//d 由于 13 不是2的倍数,所以空一位出来。占用 14,15两个位置
//e 占用4个字节。 16,17,18,19
//这里嵌套了结构体,并且LGStruct1里面最大的数据类型为double、8字节。所以开始的位置要为8的整数倍,而20不是整数倍,所以需要空出来几个字节,从24开始存,且LGStruct1占用24字节。所以24-47 存放LGStruct1。
//所以按照最大的8字节对齐就是 48 字节。

情况二:结构体里面嵌套结构体和结构体指针

struct LGStruct4 {
    double a;               //8     [0  7]
    int b;                  //4     [8 9 10 11]
    char c;                 //1     [12]
    short d;                //2     (13 [14 15]
    int e;                  //4     [16 17 18 19]
    struct LGStruct2 *str;  //8     (20 21 22 23 [24 ... 31]
    char f;                 //1     [32]
    struct LGStruct1 str2;  //18字节    (33 ... 39)[40 ... 57]    --8字节对齐为: 64
}struct4;
//由于指针占用的位置大小为8字节。所以和其他数据类型基本维持一样
打印结果

3、验证对齐的原则

上面提到了第三点:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。

//情况三结构里面全是小数据 最大为4字节
struct LGStruct5 {
    int a;      //4     [0 1 2 3]
    char b;     //1     [4]
    short c;    //2     (5 [6 7]
    bool d;     //1     [8]
    int e;      //4     (9 10 11 [12 13 14 15]  //16
    char f;     //1     [16]        //20 (4*5) //最大为成员4字节
}struct5;
//这里实际到了17字节。如果按照4字节对齐就是20;如果是8字节就是24。

第二点:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储

struct LGStruct6 {
    int a;                  //4     [0 1 2 3]
    char b;                 //1     [4]
    short c;                //2     (5 [6 7]
    bool d;                 //1     [8]
    struct LGStruct1 str1;  //实际18字节,成员最大为8(9 ... 15)[16 ... 33];
    // 实际占用 34字节; LGStruct1里面最大为8;8字节对齐为:40
}struct6;
//同理这里本内部数据类型最大为4字节。但是`LGStruct1` 占用的大小为24,而内部最大数据类型为8;所以如果按照内部最大为8,那么就应该从8的倍数16开始存,由于里面实际占用的有17字节。
打印结果
struct LGStruct7 {
    double a;               //8     [0  7]
    int b;                  //4     [8 9 10 11]
    char c;                 //1     [12]
    short d;                //2     (13 [14 15]
    long e;                 //8     [16 ... 23]
    char f;                 //1     [24]
    //如果按照4*7 = 28 ,没有8*4 = 32
    struct LGStruct5 str1;  //17字节,4字节对齐  (25 26 27 [28 44]
    // 实际占用45字节; 最后内部成员最大为a(bouble 8 字节);所以8字节对齐为: 48
}struct7;

struct LGStruct8 {
    int a;                  //4     [0 1 2 3]
    char b;                 //1     [4]
    short c;                //2     (5 [6 7]
    int d;                  //4     [8 10 11 12]
    bool e;                 //1     [13]
    struct LGStruct5 str1;  //17字节,4字节对齐  (14 15 [16 ... 32]
    // 实际占用33 字节; 最大4字节对齐: 36
}struct8;
打印结果

NSObject 占用的内存大小问题

如下所示,创建两个对象,并且创建一部分属性

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;      //8
@property (nonatomic, copy) NSString *nickName;  //8
@property (nonatomic, copy) NSString *hobby;     //8
@property (nonatomic, assign) int     age;       //4
@property (nonatomic)       double    height;    //8
@property (nonatomic) char c1;                   //1
@property (nonatomic) char c2;                   //1
@property (nonatomic, assign) BOOL sex;          //1

@end

@interface LGTeacher : NSObject

@property (nonatomic) char c1;                   //1
@property (nonatomic, copy) NSString *name;      //8
@property (nonatomic, assign) BOOL sex;          //1
@property (nonatomic, copy) NSString *nickName;  //8
@property (nonatomic, copy) NSString *hobby;     //8
@property (nonatomic, assign) int     age;       //4
@property (nonatomic)       double    height;    //8
@property (nonatomic) char c2;                   //1

@end

然后我们打印看看结果:


打印结果.png

我们尝试把属性修改为成员变量,再来看看结果如何

//看看OC对象的内存大小问题
@interface LGObject1 : NSObject
{
    NSString *name;             //8
    NSString *nickName;         //8
    NSString *hobby;            //8
    int age;                    //4
    double height;              //8
    char c1;                    //1
    char c2;                    //1
    bool sex;                   //1
}
@end

@interface LGObject2 : NSObject
{
    char c1;                    //1
    NSString *name;             //8
    NSString *nickName;         //8
    bool sex;                   //1
    NSString *hobby;            //8
    int age;                    //4
    double height;              //8 
    char c2;                    //1
}
@end
打印结果

从上面的打印结果可以看到。属性的内存对齐是优化过的,而成员变量是没有优化过的。


总结

内存对齐是按照一套规则对内存进行内存分配,并且按照8字节对齐。cpu按照8字节读取是为了提高cpu的读取速度。
而系统实际分配内存的时候是按照16字节对齐的。malloc_size

上一篇下一篇

猜你喜欢

热点阅读