手撕 iOS 底层04 -- 内存对齐原理

本章主要由结构体内存对齐到苹果的属性重排, 以及16字节对齐算法

0x00 -- 获取内存大小的三种方式

LGPerson *person = [LGPerson alloc];
person.name      = @"Cooci";
person.nickName  = @"KC";
NSLog(@"%@ - %lu - %lu - %lu",person,sizeof(person),class_getInstanceSize([LGPerson class]),malloc_size((__bridge const void *)(person)));
// 输出 8 - 40 - 48

获取内存大小的三种方式:

  • sizeof(expression-or-type)

小提示 : sizeof的三种语法形式:

        int a = 10;
        size_t a1 = sizeof(a); // 4
        size_t a2 = sizeof a;  // 4
        size_t a3 = sizeof(int); // 4
        int *pa = &a; 
        size_t p1 = sizeof(pa); // 8
        size_t p2 = sizeof pa;  // 8
        size_t p3 = sizeof(int *); // 8

        NSObject *obj = [NSObject alloc] ; 
        size_t o1 = sizeof(obj); // 8
        size_t o2 = sizeof obj; // 8
        size_t o3 = sizeof(NSObject*);// 8

sizeof()是操作符,不是函数;其作用是返回一个对象或者类型所占的内存字节数。

基本数据类型int,char,double,float等这样简单内置数据类型,它们的大小和系统相关, 不同系统下取值也不一样。

如果是结构体,sizeof涉及到字节对齐的问题,根据计算机组成原理教导我们这样有助于加快计算机的取数速度,否则多话指令周期。让宽度为2的节本数据类型short等,都位于能被2整除的低智商;让宽度为4的基本数据类型int等,都位于能被4整除的地址上,以此类推, 这样,俩个数中间就可能需要加入填充的字节,所以整个结构体的sizeof值就增长了。详细对齐规则看文章下方结构体对齐。 这里不再赘述。

  • class_getInstanceSize
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());
}

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

这个函数是runtime提供的一个API函数,获取类的实例对象所占用的内存大小

通过源码可知道 class_getInstanceSize函数获取的的对象大小是8字节对齐。

这个函数依据对象内部的属性变化而变化;如果没有属性, 只继承了NSObject,则类的实例对象实际占用的内存大小是8

  • malloc_size

malloc_size是系统实际开辟的内存空间,class_getInstanceSize是实际占用的空间。

根据文章顶部代码实例1打印可验证。这个是由系统完成的,从上面的分析看得出, 实际占用和实际分配的大小是不一样的。


0x01 -- 结构体内存对齐

这里可以参考我之前写的文章底层必备c/c++知识结构体

struct LGStruct1 {
    double d; // 0-7
    char c;     //8
    int i;
    short s;      //9 10 11 12 13 14 15
};

struct LGStruct2 {
    double d;           //8 0-7
    int i;              //4 8-11
    char c;             //1 12
    short s;            //2 13 14 15
};

int main() {
        NSLog(@"%lu", sizeof(struct LGStruct1)); // 24
              NSLog(@"%lu", sizeof(struct LGStruct2));  // 16
  return 0;
}

上面片段代码Test1Test2sizeof是多少?

从打印可得知LGStruct1的内存大小为24; LGStruct216;

俩个结构体 ,数据类型一致,顺序不一样, 导致输出的结果也不一样, 这就涉及到内存对齐

至于为什么系统要做这件事; 上面说sizeof的时候也说了, 简单来说,提高性能。

内存对齐规则

  • 【原则一】 数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), nm 位置开始存储, 反之继续检查 m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。

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

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

对齐规则验证

根据上面的实例代码, 画了一章对齐示意图

结构体内存对齐示意图.png
  • 根据规则一 进行内部成员对齐。所占用字节18个;
  • 根据规则三 整体对齐后,占用字节24个。

结构体嵌套对齐

struct mystruct4 {
    int a;
    struct mystruct5 {
        short c;
        double b;
        char c1;
    }str5;
    int d;
}s4;

printf("mystruct4内存大小 %lu\n", sizeof(s4)); // 40
printf("mystruct5内存大小 %lu\n", sizeof(s4.str5)); // 24

结构体嵌套对齐的算法规则是

  1. int0开始, 占4个字节, 位置是0 1 2 3
  2. str5按照规则二从double算, 空白填充4 5 6 7;从8-24是嵌套结构体所占大小。
  3. 接着25,不满足规则一,25 26 27填充空白, 28 29 30 31占位。
  4. 一共使用了32个字节,按照规则三整体对齐,是40个字节。

0x02 -- 内存优化(属性重排)

x指令 打印对象内存地址 == memory read person

x/4gx 以16进制打4个印人可以方便看的内存

@interface Other: NSObject
  @property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic) char sex;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char garde;
@end
  @implementation
  @end

Other *person = [Other alloc];
person.name      = @"liming";
person.nickName  = @"ll";
person.sex = "m";
person.garde = "A";
person.age = 23;
x:8gx.jpg

通过LLDB命令查看对象的内存布局。x命令查看的不够直观,因为iOS是小端模式,所以是内存值是倒着,使用x/4gx打印出来的方便直接查看。

查看对象内存.jpg

通过打印,把对象存储的值都直观的输出了, 而且在0x000000170000414d这个值里存储了对象里属性三个值,这就是属性重排,虽然对象编译到底层是结构体,按照结构体对齐,会极大的浪费空间 ,苹果在这一层又做了优化,就是属性重排,达到优化空间的目的。利用空间换时间

参考

结构体内存对齐


欢迎大佬留言指正,码字不易,觉得好给个赞 有任何表达或者理解失误请留言交流;共同进步;

你可能感兴趣的:(手撕 iOS 底层04 -- 内存对齐原理)