iOS底层之 内存对齐

数据类型大小

首先附上C和OC,各数据类型的大小表。

C OC 32位 64位
bool BOOL(64位) 1 1
signed char (__signed char)int8_t、BOOL(32位) 1 1
unsigned char Boolean 1 1
short int16_t 2 2
unsigned short unichar 2 2
int int32_t、 NSInteger(32位)、boolean_t(32位) 4 4
unsigned int boolean_t(64位)、 NSUInteger(32位) 4 4
long NSInteger(64位) 4 8
unsigned long NSUInteger(64位) 4 8
long long int 64_t 8 8
float CGFloat(32位) 4 4
double CGFloat(64位) 8 8

为什么要做内存对齐

如下图,|-|表示自然边界,假设存在两个连续的位于自然边界上的64位数据,当CPU访问它们中的任何一个的时候,可以一条LD指令就能完成加载:
  |-|BBBBBBBB|-|BBBBBBBB
现在假设有一个8字节数据如下,|表示数据开始位置,|-|表示自然边界:
  |-|BBBBB|BBB|-|BBBBB|BBB
其前三字节为前一个对齐的八字节数据的后三字节,其后五字节为后一个对齐的八字节数据的前五字节。
对于不支持非对齐装载指令的CPU来说,要装载这样的一个数据,需要先装载前一个八字节数据,再装载后一个八字节数据,然后将前一个八字节数据的后三字节与后一个八字节数据的前五字节数据合并才能得到结果,与对齐数据的访问相比,多了一个装载指令以及相关合并指令的开销,一般来说,在忽视缓存未命中的情况下,装载指令的执行与得到结果之间是存在额外开销的,因此这个差别是很大的,何况上边说的,假设是在操作系统对CPU异常进行处理时为其加载数据,那么异常处理程序的开销可能更大;对非对齐数据的写入时也需要额外的加载,合并操作。即使对于支持非对齐数据加载的CPU,依然会极大的影响效率,差别只是它省略掉了CPU异常处理过程。
  再进一层,假设之前描述的非对齐数据刚好横跨两个cache line,而且这两个cache line至少有一个不在cache中(虽然对齐数据也会存在未命中,但是与非对齐相比,它不会横跨两个cache line),那么这个访问效率绝对不是多几十条指令的问题了。因此,内存不对齐的坏处不是浪费内存,因为即使我写一个随便在不同位置放置不同大小的数据结构时,只要告诉编译器说必须按照一字节对齐,编译器编译时肯定按照我的意愿不浪费一个字节的内存。编译器默认按照自然边界对齐,是因为它要求效率,保证程序的正常运行(因为非对齐访问可能导致进程退出)。我们对结构体的组织的调整是为了节约内存,而调整的规则就是按照内存对齐来安插数据。

内存对齐的规则

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
    ⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
    从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,
    结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存
    储;
  • 结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
    其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b
    ⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.);
  • 收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
    成员的整数倍.不⾜的要补⻬。

是不是很抽象?是不是很无语?看的是不是很困?其实我也是。。
稍微皮一下。。

下面就来做一下图文解析。。


15996337662730.jpg

开篇的时候,就已经附上了一个数据类型大小的表格,double占8字节、char占1字节、int占4字节、short占2字节。

  • 第一个数据成员a,从offset为0的位置开始存储,共8个字节;
  • 第二个数据成员b,从offset为8的位置开始存储,是char类型大小1的倍数,所以可以直接存储1个字节;
  • 第三个数据类型c,从offset为9的位置开始,但是由于9并不是int类型大小4的倍数,所以需要从后面的位置12开始存储四个字节,即12-15的位置;(上述规则1)
  • 第四个数据类型d,从offset为16的位置开始存储,是short类型大小2的倍数,所以可以直接存储2个字节,即16和17两个位置。

但是,根据上述规则3:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
成员的整数倍.不⾜的要补⻬,此时结构体总大小为18,不是该结构体内部最大成员的整数倍,因此需要取比18大的最小的8的整数倍,也就是24.

扩展

老规矩,先附上图:


15996355351685.jpg

当结构体内部包含结构体成员的时候,计算方式略有不同(参考上述规则第二条),其中非结构体成员的计算规则不变。

  • 如上图所示,结构体之前的a、b、c存储到了第11位;
  • 根据上述规则2,结构体成员存储开始位置需要是该结构体内部成员的最大成员大小,当前例子中即为8的整数倍,因此,从16位开始存储,其中16-23位为struct1.a,24和25为struct1.b;
  • d的存储规则和之前一样,从32位开始,存储到35位。

最后,根据上述规则3:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
成员的整数倍.不⾜的要补⻬,此时结构体总大小为35,不是该结构体内部最大成员的整数倍,因此需要取比35大的最小的8的整数倍,也就是40.

你可能感兴趣的:(iOS底层之 内存对齐)