前言
这是我参与更文挑战的第2天,活动详情查看: 更文挑战 本篇主要针对在探索
alloc过程中碰到的两个点,进行更深入的研究。
附上上篇的链接:iOS底层探索之alloc(上)
内存对齐
内存对齐的三大原则
1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 ⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要 从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存 储。
2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从 其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b ⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤ 成员的整数倍.不⾜的要补⻬。
验证
下面定义两个结构体,用三个原则去推算各自占用内存大小,然后在对比打印的结果。
struct Colors1 {
char yellow; //1个字节 offset:0 , 到0
double red; //8个字节,offset:8 , 到15
int blue; //4个字节 offset:16 , 到19、
}; // 总大小为20个字节,内存最大为8字节,补齐为24
struct Colors2 {
char yellow; //1个字节 offset:0 , 到0
int blue; //4个字节,offset:4 , 到7
double red; //8个字节,offset:8 , 到15
}; // 总大小为16个字节,内存最大为8字节,正好是8的倍数,故为16
struct Colors3 {
char yellow; //1个字节 offset:0 , 到0
double red; //8个字节,offset:8 , 到15
int blue; //4个字节 offset:16 , 到19
struct Colors1 color; // 最大为double 8字节,offse 24 到48
}; // 总大小为48个字节,内存最大为8字节,正好是8的倍数,故为48
struct Colors1 color1;
struct Colors2 color2;
struct Colors3 color3;
NSLog(@"%lu",sizeof(color1));
NSLog(@"%lu",sizeof(color2));
NSLog(@"%lu",sizeof(color3));
附上color1内存排列位置图,
打印的结果跟上面一致。
这里附上常见数据类型占用字节大小的表
为什么要内存对齐
- 性能原因
cpu在读取内存的时候是一块一块来读取的,块的大小可以是2,4,8,16字节大小,块大小成为memory access granularity(可翻译为内存读取粒度)。
如果color2并没有内存对齐,属性在内容中排列如图所示,
若此时
cpu内存读取粒度为4,当cpu读取blue时,要先读取[0-3],接着读取[4-7],再[0,3]偏移一个字节,加上[4-7]的第一个字节才能得到blue的值,大大降低了cpu的性能。
内存对齐也是典型的空间换时间的优化。
- 还有一些平台移植的原因,这里没有过多的研究,有兴趣的同学可以自行查资料了解。
alloc为什么会先调用objc_alloc
看过上一篇文章同学会知道
alloc流程中会先调用objc_alloc,很好奇这是为什么。 首先猜测是运行时做的手脚,全局搜索关键字objc_alloc,最后定位到这个方法,很像是这个方法做了手脚,直接下断点运行,结果并没有运行到这里。
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
那么可能是编译时做的手脚,于是我将"main.m"编译成"IR"文件时发现原本的alloc,编译之后变成了objc_alloc,由此可见llvm确实在编译时,对alloc [[某某 alloc ] init] allocWithZone做了处理。但由于自身水平有限,无法参透苹果这么做的原因。
命令:clang -S -fobjc-arc -emit-llvm main.m