OC对象(二)-- 内存对齐和calloc中的16字节对齐

OC对象(一)-- alloc和init底层到底在干嘛
OC对象(二)-- 内存对齐和calloc中的16字节对齐
OC对象(三)-- isa结构分析

内存对齐初探


实例对象在内存中的布局,是被系统优化过的,不会按照属性定义的顺序在内存中开辟空间。
举个例子:
定义一个Person类,里面包括一些属性

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) char ch1;
@property (nonatomic, assign) char ch2;
@end

@implementation Person
@end

初始化实例对象,给属性进行赋值:

Person *p = [Person alloc];
p.name = @"DragonetZ";
p.nick = @"DZ";
p.age = 18;
p.ch1 = 'a';
p.ch2 = 'z';
NSLog(@"p:%@", p);

使用lldb指令x/4gx p查看实例对象的内存情况,p是实例对象的指针变量,打印结果如下:

(lldb) x/4gx p
0x600000683e20: 0x00000001061fb6c8 0x0000001200007a61
0x600000683e30: 0x00000001061f9018 0x00000001061f9038
  • 第一个值0x00000001061fb6c8:实例对象的isa
  • 第二个值0x0000001200007a61:里面存放着age、ch1、ch2的值,都是使用十六进制表示。0x12对应十进制18。0x7a对应的十进制122,ASCII中对应的就是‘z’,同理0x61对应的97,就是‘a’
  • 第三个值0x00000001061f9018:就是name属性值‘DragonetZ’
  • 第四个值0x00000001061f9038:就是nick属性值‘DZ’

如图中展示,内存中的排序和类型属性定义的顺序不一致。

拓展-lldb命令解释


上文用了lldb指令x/4gx p,这里做一个简单解释:

  • p:读取实例对象p的内存。也就是读取内存的起始位置。
  • 第一个x:是memory read指令的简写,读取内存作用
  • 4g:从起始位置开始,读取4段
  • 第二个x:代表的是16机制的方式读取,同理可以切换成其他进制模式:‘o’代表八进制,‘t’代表二进制,‘d’代表十进制。

打印内容:


  • 冒号左侧,也就是图中的红色框中代表的是内存地址,第一个地址也就是p的首地址。与po p的打印是相同过的。
  • 冒号右侧,是地址中的值。

内存对齐原则


OC中,实例对象其实就是struct类型,因此我们研究一下struct是如何进行内存对齐的

简单的小案例

struct Struct1 {
    double a;
    char b;
    int c;
    short d;
}stu1;

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;

NSLog(@"%lu - %lu", sizeof(stu1), sizeof(stu2));

定义两个struct,每个struct中都有几个不同类型的成员,打印两个struct的内存占用情况。


stu1占用24个字节,stu2占用16字节。

此处可以使用下图来自己先计算一下:


基础数据类型内存占用表

原理知识点

  1. struct第一个数据成员,从偏移量offset的0位开始。后续的成员从自身的整数倍的偏移位置开始。
  2. 计算出来的总大小,需要是最大成员的整数倍。

用stu1解释说明:

struct Struct1 {
    double a;
    char b;
    int c;
    short d;
}stu1;
  • double a是第一个成员,占用8个字节,根据说明,第一个成员offset是0,占用空间【0-7】
  • char b,占用1个字节,offset是8,而且开始位置8是需要占用空间1的整数倍,所以可以存放【8】
  • int c,占用4个字节,offset是9,开始位置9不是需要占用空间4的整数倍,需要后移到整数倍12上存放,占用的位置是【12-15】
  • short d,占用2个字节,offset是16,16是2的整数倍,占用【16-17】
  • 占用【0-17】共18个字节,成员中最大的是double,占8字节,所以取8的整数倍,就是24字节。

再来看看stu2:

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;
  • double a,占用【0-7】
  • int b,【8-11】,因为8是4的整数倍。
  • char c,【12】
  • shot d,【14-15】,因为开始位置13不是2的整数倍,因此从14开始
  • 占用【0-15】,共16字节,最大成员double是8字节,取8整数倍,正好是16

扩展-struct嵌套

如果struct a中有另一个struct b作为它的成员,那么偏移量offset就取struct b中最大成员的整数倍开始

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;

struct Struct3 {
    char a;
    short b;
    struct Struct2 c;
}stu3;

struct Struct2前面分析结构:占用16个字节,最大成员是8字节
分析stu3

  • char a:【0】
  • short b:【2-3】
  • struct Struct2 c:找到8的整数倍作为开始位,【8-23】
  • 取最大成员8的整数倍,也就是24

OC底层的优化

上面的例子中stu1和stu2两个struct可以说是差不多,但是一个占用24,一个占用16。但是我们在OC类的时候,属性顺序是不受影响的。说明苹果底层是对我们内存开辟进行优化过的。这里可以通过示例对象内存打印中可以发现,文章开始的例子中:


Person类中属性age、ch1、ch2的值都存放在内存中第二个位置(0x0000001200007a61)中。

calloc


之前文章alloc和init底层到底在干嘛!中分析了alloc流程,先是用16字节对齐的方式计算出size,然后调用calloc函数来开辟内存空间

//16字节对齐获取到size
size = cls->instanceSize(extraBytes);

//根据size开辟内存空间
obj = (id)calloc(1, size);

此时产生一个问题,如果传入的size没有进行16字节对齐,也就是说传入的不是16字节的倍数,会是什么情况?

测试代码

#import 

void *temp = calloc(1, 40);
NSLog(@"%lu", malloc_size(temp));

调用calloc方法,第二个参数传入40,注意这个值不是16的倍数。
运行结果:


通过结果可以看出,calloc里面也会进行16字节对齐,接下来我们来找找这16字节对齐的代码。

查看源码

首先先看看calloc函数在哪个源码中,用⌘+鼠标左键,属于malloc源码中


下载源码libmalloc-283.100.6

源码中大致的流程,如图:


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; // 16
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // (size + 16 -1)右移4位 SHIFT_NANO_QUANTUM=4
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // 左移4位
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

这个函数就是16字节对齐的函数:

  • 先判断size如果等于0,就给一个默认值16,NANO_REGIME_QUANTA_SIZE是个宏。
  • 用size进行计算,size+16-1,将结果右移4位。然后在左移4位。目的是将低4位抹零。通过左右位移4位,来实现16字节对齐。

你可能感兴趣的:(OC对象(二)-- 内存对齐和calloc中的16字节对齐)