iOS底层探索 02-内存对齐原理

一. 结构体引入

结构体是由基本数据类型或者指针,数组,结构体/联合体/枚举等类型组成的一种结构,我们可以简单地定义一个结构体如下

struct Person {
  char *contact;
  bool sex;
  short int age;
  int height;
  char  name[9];
};

二. 结构体所占内存大小

结构体的大小主要与操作系统地址总线宽度编译器对齐方式相关。

各种数据类型在ios中的占用内存大小


image

在64位系统下探索结构体占用的内存大小


    struct  Person stu;
    stu.sex = NO;
    stu.age = 18;
    stu.contact = "[email protected]";
    stu.height = 20;
    stu.name[0]= 65;
    stu.name[1]= 66;
    stu.name[2]= 65;
    stu.name[3]= 66;
    stu.name[4]= 65;
    stu.name[5]= 66;
    stu.name[6]= 65;
    stu.name[7]= 66;
    stu.name[8]= 65;
    printf("%lu",sizeof(stu)); //32

上面代码打印出的stu所占内存为32个字节,

首先将断点设置在如图位置,当断点断住时,在调试区域可以看到stu对象,右击stu,选中View Memory of "stu",可以查看stu的内存分配


image

从内存地址中的值,可以观察到stu的内存分配:


image

成员变量在内存中的存储位置为:


image
  • contact; 是个指针,在64位系统下占用8个字节

  • sex,age,height 共同占用8个字节
    sex;实际占用一个字节,但需要字节对齐,sex后会空一个字节
    age 占用2个字节,
    height;占用8个字节

  • name 实际占用9个字节,但会字节对齐,其后会预留7个字节,以达到8字节的对齐

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

  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

三. 结构体嵌套的内存大小


struct Age {
    int age;
    bool sex;
};
struct Human {
    char  name;
    struct Age a1;
};

结构体Age的占用的内存大小在64位系统下是8个字节,占用内存最大的成员变量是age,占用4个字节;
结构体Human的尺寸在64位系统下占用12个字节,由于结构体Age中最大的成员变量是age,占用4个字节;所以name占用一个字节后,将空三个自己,然后再开始存储a1;

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

比如int为4字节,则要从4的整数倍地址开始存
储。

min(当前开始的位置m,n)m=9n=4 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m = m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。

  • 数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8

  • 最后结构体的内存大小必须是结构体中最大成员内存大小整数倍,不足的需要补齐

案例分析:


image

四. OC类的内存优化

定义一个自定义CJLPerson类,并定义几个属性,
@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@implementation LGPerson

@end

在OC类中声明的实体属性会转化为数据成员。每个OC类中还会有一个隐式的数据成员isa,这是一个指针类型的数据成员,并且是作为类的第一个数据成员被定义。

OC类中定义的实例属性,系统在编译时会创建一个带下划线的数据成员,属性数据成员的内存排列顺序会被优化处理。
存储顺序为: isa,c1,c2,age,name,nickname,height

直接定义内部的数据成员不参与排序优化,比如下面的形式:


@implementation LGPerson {
   //内部的数据成员
    int type;
    NSString  *sencondName;
}
@end

上面的实现中定义了两个内部数据成员type,sencondName。当出现这种情况时编译器不会对这些内部数据成员的顺序进行优化,而是按定义的顺序在内存中进行排列,并且是优先于属性数据成员进行排列,type和sencondName排列在isa之后,其他实例属性之前.

存储顺序为: isa,type,sencondName,c1,c2,age,name,nickname,height
注意:type会占用8个字节

五. iOS中获取内存大小的三种方式

  • sizeof:操作符,传入的可以是数据类型和对象,编译阶段就可以确定其大小
  • class_getInstanceSize:用于获取类的实例对象所占用的内存大小,并返回具体的字节数,其本质就是获取实例对象中成员变量的内存大小,obj4中会进行8字节对齐
/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);



size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}



// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}



static inline uint32_t word_align(uint32_t x) {
    //8字节对齐 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


  • malloc_size:获取系统实际分配的内存大小,64位系统下会进行16字节对齐

六. 内存对齐

6.1#pragma pack(n)

每个平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,在ios中,Xcode默认为#pragma pack(8),即8字节对齐
通过预编译命令#pragma pack()取消自定义字节对齐方式

#pragma pack(1) //编译器1字节对齐存储
struct Person
{
char sex;
short age;
float weight;
char c1
};
#pragma pack() //取消1字节对齐,恢复默认对齐
//sizeof(Person)  = 8

struct Human
{
char sex;
short age;
float weight;
char c1;
};

//sizeof(Human) = 12

6.2 __attribute__((aligned (n)))

__attribute__((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

__attribute__((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

#define PACKED __attribute__((packed))
struct PACKED Person {//取消字节对齐
    char sex;
    short age;
    float weight;
};
#pragma pack() //恢复默认对齐
//sizeof(Person)  = 7 ,其中sex占一个字节,age占两个字节,weight占4个字节

#define PACKED8 __attribute__((aligned (8)))
struct PACKED8 Student{
    char sex;
    short age;
    float weight;
    char c1;
};
#pragma pack() //恢复默认对齐
//sizeof(Student)  = 16  其中sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙7个字节


struct Human{
    char sex;
    short age;
    float weight;
    char c1;
};
    //sizeof(Human) = 12 ,其中sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙三个字节

可以定义一个Student对象来断点查看下内存:

 struct Student stu;
    stu.sex = 'M';
    stu.age = 18;
    stu.weight= 50.0;
    stu.c1 = 'A';

内存的存储情况如下:sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙7个字节
[图片上传失败...(image-b91ff4-1600327993148)]

6.3 16字节对齐算法1

static inline size_t align16(size_t x) {
   return (x + size_t(15)) & ~size_t(15);
}

6.4 16字节对齐算法2

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

6.5 8字节对齐算法

static inline uint32_t word_align(uint32_t x) {
  // 8字节对齐 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


你可能感兴趣的:(iOS底层探索 02-内存对齐原理)