iOS底层之内存对齐算法解析

目前但凡一个iOS岗面试都会问个内存对齐问题,那么什么是字节对齐?成员变量对齐和对象内存对齐有什么区别?今天我来为大家一一解析

iOS创建对象的_class_createInstanceFromZone方法中会通过instanceSize方法计算创建对象需要开辟的内存空间(细创建对象流程点我),源码如下:

inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

alignedInstanceSize()源码如下:

uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

通过分析源码得出以下结论:

  1. 对象开辟的空间大小完全取决于对象的成员变量(depending on class's ivars),通过成员变量得出未对齐的内存大小:
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
  1. 若对象没有成员变量,则属性只有从基类NSObject继承来的isa,isa为一个class类型的结构体指针Class isa OBJC_ISA_AVAILABILITY;,因此一个对象最小的未对齐空间unalignedInstanceSize为8。

  2. 得到unalignedInstanceSize以后,通过word_align方法将其字节对齐,接下来逐步分析具体成员变量对齐方法

  • 对齐方法代码:
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

其中WORD_MASK定义为define WORD_MASK 7UL

  • 假设当前对象没有成员变量,则x = 8,以上表达式可转化为:
   (8 + 7) & ~7
->  15 & ~7
  • 转为二进制则为:
15      : 0000 1111
7       : 0000 0111 
~7      : 1111 1000
15 & ~7 : 0000 1000 = 8

一个无成员变量的对象通过对齐得到的alignedInstanceSize为8。

  • 字节对齐算法的意义为8字节对齐,取8的整数倍。目的在于在计算读取成员变量的时候,如果成员变量根据实际类型设定大小,会影响计算机读取速度,但如果统一以8字节为度量衡,可以加快读取速度(以空间换取时间)这个算法也可以用以下方式表达:
  (8 + 7) >> 3 << 3           右移3位再左移三位
  15 : 0000 1111
  右移三位 -> 0000 0001
  左移三位 -> 0000 1000 = 8
  • 得到alignedInstanceSize后,instanceSize最后会通过if (size < 16) size = 16条件将小于16的结果全部转为16,至此,一个无成员变量的对象开辟的空间为16。

4.接下来分析对象带有多个成员变量的情况:

@property (nonatomic,copy)NSString *name;

@property (nonatomic,assign)int age;

@property (nonatomic,assign)long height;

@property (nonatomic,copy)NSString *nickName;

@property (nonatomic,assign)BOOL isMale;

并进行赋值:

p.name = @"FC";
p.age = 18;
p.height = 185;
p.nickName = @"XX";
p.isMale = YES;

在控制台通过x/6gx p 来打印对象p的详细内存情况:


image.png

其中第一段内存为对象的isa,第二段内存中,系统将属性age和属性isMale两个4字节的属性进行了组合,优化了内存分配,打印结果如图:

image.png

5.内存对齐原则:

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
    从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。

  • 结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
    其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b
    ⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)

  • 收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
    成员的整数倍.不⾜的要补⻬。

举例:


image.png

以上两个结构体内存大小是否一致?答案:不一致,分别为24和16。
为什么同样的成员变量,因为顺序不同内存结果不同?
依据原则1”每个成员变量储存的起始位置需要可以整除该成员变量的字节大小“,得出两个结构体的储存流程如下:


image.png

image.png

再依据原则3,内存结果必须为最大成员的整数倍,得出结果为24和16。

为什么每个成员变量起始位置必须要是自己的整数倍呢?


image.png

不做对齐操作时,计算机读取此结构体变量需要读取4次,每次读取大小分别为8,1,2,4,读取次数多,每次读取次数不同,降低读取效率。

image.png

内存对齐操作后,可以看到后面3个成员变量组合到了一起,并且系统会告知该组合中含有大小为1,2,4的成员变量,计算机再读取此结构体的时候只需以8字节为单位读取两次,大大提高了读取效率,这就是需要内存对齐的原因啦

6.对象内存最终结果

  • 创建一个类:


    image.png
  • 如图赋值并进行打印:


    image.png
  • 打印结果为:


    image.png

按照之前的分析:
sizeof(p)结果为8(p为结构体指针,大小为8);
class_getInstanceSize([FCPerson class])为成员变量大小+isa,结果为40
malloc_size是什么?为什么等于48?

通过研究malloc源码, 在calloc方法最后调用的segregated_size_to_fit方法中:

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;
}

size为传入的40,NANO_REGIME_QUANTA_SIZE为16, SHIFT_NANO_QUANTUM为4。
slot_bytes就是返回的最终的对象大小,其算法为: (40 + 16 - 1) >> 4 << 4,即对40进行16字节对齐,最终结果为48。

16字节对齐的原因:若以8字节对齐,64字节以内,可以存放8个8字节对象(8,16,24,32,40,48,56,64),对象之间两两相邻。若以16字节对齐,64字节内可以放4个对象(16,32,48,64),对象两两相邻的次数降低,减少系统误指的概率。另一方面,一个对象的成员变量最小为8(isa),但只含有默认变量isa的对象概率极低,实际使用中绝大部分类都有额外的成员变量,若以8字节对齐则每个对象内存都要做额外计算,因此直接使用16字节对齐会提高效率。

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

推荐阅读更多精彩内容