iOS 底层探究二:内存对齐1

795 阅读5分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

内存对齐的原则

1.数据成员对齐规则

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

2.结构体作为成员

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

3.首位工作

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

基本数据类型的内存大小

COC32位64位
boolBOOL(64位)11
signed char(__signed char)int8_t、BOOL(32位)11
unsigned charBoolean11
shortint16_t22
unsigned shortunichar22
int 、int32_tNSInteger(32位)、boolean_t(32位)44
unsigned intboolean_t(64位)、NSUInteger(32位)44
longNSInteger(64位)48
unsigned longNSUInteger(64位)48
long longint64_t88
floatCGFloat(32位)44
doubleCGFloat(64位)88

如何获取内存大小

1. sizeof(expression-or-type)

sizeof()C/C++中的关键字,它是一个运算符,不是函数。作用是取得一个对象(数据类型或者数据对象)的长度(即占用内存的大小,以byte为单位,返回size_t)。基本数据类型(intdouble等)的大小与系统相关。结构体涉及字节对齐。

示例:

    struct Stu {

        char c;
        int i;
        double d;
    };

    void test() {

        //基本数据类型
        int age = 18;
        size_t sizeAge1 = sizeof(age);
        size_t sizeAge2 = sizeof age;
        size_t sizeAge3 = sizeof(int);
        NSLog(@"age size: %zu, %zu, %zu",sizeAge1,sizeAge2,sizeAge3);

        //结构体
        struct Stu s;
        s.c = 'c';
        s.i = 18;
        s.d = 180.0;

        size_t sizeS1 = sizeof(s);
        size_t sizeS2 = sizeof s;
        size_t sizeS3 = sizeof(struct Stu);
        NSLog(@"s size: %zu, %zu, %zu",sizeS1,sizeS2,sizeS3);

        //指针
        NSObject *obj = [NSObject alloc];
        size_t sizeObj1 = sizeof(obj);
        size_t sizeObj2 = sizeof obj;
        size_t sizeObj3 = sizeof(NSObject *);
        NSLog(@"obj size: %zu, %zu, %zu",sizeObj1,sizeObj2,sizeObj3);
    }

输出:

age size: 4, 4, 4
s size: 16, 16, 16
obj size: 8, 8, 8
  • 通过类型和实例都可以获取内存大小,这也说明开辟的内存大小在类型确定后就已经确定了。
  • sizeof是运算符不是函数。3种语法形式都可以,需要注意的是通过类型获取的方式必须在()中。

2. class_getInstanceSize

这个函数是rutime提供的获取类的实例锁占用的内存大小。大小至于成员变量有关。获取的是实际占用的空间(8字节对齐)。

3. malloc_size

malloc_size就是alloc中实际开辟的空间。

案例份分析

1.有如下结构体Struct1Struct2分别占用多大内存?

struct Struct1 {
    double a; // [0,7]
    char b;     // [8]
    int c;        // 根据第一准则要从4的倍数开始,所以[12,13,14,15]。跳过9,10,11
    short d;  //[16,17]
}struct1;
//根据第三准则总大小要是8的倍数,那就要分配24字节。

struct Struct2 {
    double a; //[0,7]
    int b;        //[8,11]
    char c;     //[12]
    short d;   //根据准则1跳过13,从14开始 [14,15]
}struct2;
//这里0~15大小本来就为16了,所以不需要补齐了。

验证:

NSLog(@"struct1 size :%zu\nstruct2 size:%zu",sizeof(struct1),sizeof(struct2));

输出:

struct1 size :24
struct2 size:16
  • 结构体中数据类型顺序不一致占用的内存大小可能不一致。
  • 大小计算从0开始,Struct2并没有进行第三原则补齐。

2. 增加一个Struct3中有结构体嵌套,那么占用大小是多少?

struct Struct3 {
    double a;                  //[0,7]
    int b;                         //[8,11]
    char c;                     //[12]
    short d;                   //跳过13 [14,15]
    int e;                        // [16,19]
    struct Struct1 str; //根据准则2,Struct1最大元素为`double`类型,所以从24开始。根据`Struct1`分配的时候24个字节,所以str为[24,47]
}struct3;
//所以Struct3占用内存大小为48字节。

验证:

NSLog(@"struct3 size :%zu",sizeof(struct3));

struct3 size :48

在这里有个疑问,准则3是先作用在Struct1再作用Struct3还是最后直接作用在Struct3?不妨验证一下:

struct Struct4 {
    struct Struct1 str;
    char c;
}struct4;

Struct1本身占用18字节,补齐后占用24字节。如果Struct4最终占用32字节那么就是第一种情况,张永24 字节则是第二种情况。

NSLog(@"struct4 size :%zu",sizeof(struct4));
struct4 size :32

这也就验证了猜想,结构体嵌套从内部开始补齐。这也符合常理。

3. 修改结构体如下:

struct S1 {
    int a; // 4  [0,3]
    char b;// 1  [4]
    short c; // 2 [6,7]
}; // 0~7 8字节

struct S2 {
    double a; // 8 [0,7]
    char b;   // 1 [8]
    struct S1 s1; // 8  [12,19]  按s1自身中存的最大a的4字节的倍数对齐
    bool c; // 1 [20]
};
//0~20一共21个字节,按最大的8字节对齐。应该24字节。
struct S2  s2;
NSLog(@"size :%zu",sizeof(s2));

这个时候s2大小为多少?

分析:

  • S1:

    • int a4个字节,从[0~3]
    • char b1个字节,[4]
    • short c2个字节,需要以2字节对齐,所以跳过5 [6~7]
    • S1整体从0~7不需要补齐。占8字节。
  • S2:

    • double a8字节,[0~7]
    • char b1字节,[8]
    • struct S1 s18字节。由于S1内部最大元素为int a所以需要4倍对齐,所以[12~19]
    • bool c1字节,[20]
    • S2整体从0~21一共21字节,需要按S2中最大元素double a补齐。所以应该是24字节。

输出:

size :24