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 <malloc/malloc.h>

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字节对齐。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350