1.准备工作:
苹果开源代码地址:
opensource.apple.com/tarballs/ob…
macOS11.01 objc4-818.2可编译运行源码工程:
2.调试断点的三种方式,以断点alloc为例:
2.1方式一:
- 首先在代码中添加需要断点的位置(案例中设置在了Person的alloc那一行代码);
- 然后再添加一个”alloc”符号断点,并将其设置为浅色不生效的状态(因为系统下调用alloc方法的地方很多,我们需要的是让断点精
确执行到Person的alloc方法里);
- 当断点执行到第6行时,点亮之前设置的”alloc”符号断点让其生效,然后点击断点执行按钮就会调到如下图的流程,可以看到Person类调用到的alloc方法来自于,libobjc.A.dylib+[NSObject alloc],alloc方法内执行下一个流程是跳转到_objc_rootAlloc方法,为此我们可以通过上述的源码地址找到objc开源库找到_objc_rootAlloc方法查看具体实现。
2.2方式二:
- 设置第6行代码[Person alloc] 断点,当断点执行到此处时,按住Ctrl鼠标点击断点工具栏的step into 按钮得到如下图结果,由此可知即将要调用的方法是objc_alloc
- 然后再添加一个”objc_alloc”符号断点,继续执行就会得到如下图结果,objc_alloc方法来自libobjc.A.dylib为此我们一样可以在上述的源码地址中找到objc开源库并找到这个方法
2.3方式三:
- 设置第6行代码[Person alloc] 断点,当断点执行到此处时,选择Xcode菜单栏勾选Debug->DebugWorkFlow->Awalys show Disassembly,接下来的步骤也是添加objc_alloc符号断点,来找到objc_alloc方法的出处
3.alloc和new流程图
4.字节对齐与内存对齐
4.1 结构体成员变量8字节对齐
- 先来看一段代码,在main.m文件中实现如下:
@interface Person : NSObject{
@public NSString *name;
@public int age;
@public char a;
}
int main(int argc, const char * argv[]) {
Person *p = [Person alloc];
p->name = @"LY";
p->age = 18;
p->a = 'a';
NSLog(@"--->%zu",class_getInstanceSize(p.class));
return 0;
}
复制代码
- 输出结果如下:
-
问题:
(1)为什么通过class_getInstanceSize方法得到的实例对象大小是24个字节?
(2)既然是8个字节对齐为什么一个8字节的地址段里0x0000006100000012存储了两个值?
-
具体分析:
(1)首先在终端通过clang -rewrite-objc main.m -o main.cpp指令还原得到下层C/C++的代码实现如下:
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *name;
int age;
char a;
};
复制代码
(2)本质上对象p就是一个结构体,结构体中的struct NSObject_IMPL NSObject_IVARS 就是isa继承自NSObject类的成员变量,isa是个Class类型,Class只是一个别名,本质上是一个指针类型,所以占8个字节;
NSString *name是一个指针 占8个字节;
int age 占4个字节;
char a 占1个字节;
x = 8+8+4+1 = 21
(3)class_getInstanceSize()该方法返回的是一个类实例的大小,通过3中的alloc初始化流程图可以得知在步骤word_align(x)方法中进行了一个8字节对齐算法的一个处理即:
对齐算法:(x+7)&~7; (21+7)&~7 = 24;
(4)因为苹果底层本身对存储做了优化,假如让一个字符占一个8字节的坑岂不是过于浪费,所以将 char a和 int age并到了一起,也就是按8字节进行内存对齐;至于为何要按8字节对齐有两个解释,一是在64位处理器中最大的数据类型占位是8字节,二是为了高效的按规则进行进行数据读取,空间换时间;
- 结构体大小对齐原则:
(1):数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,
结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置mn)m=9 n=4
9 10 11 12
(2):结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
(3):收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。
4.2 calloc()堆区开辟的空间以16字节对齐
- 再来看一段代码
int main(int argc, const char * argv[]) {
//开辟一段21字节的空间并赋值给指针p;
void *p = calloc(1, 21);
//获取p在堆区的内存大小
NSLog(@"---->%lu",malloc_size(p));
return 0;
}
复制代码
- 输出结果如下
- 问题:为何输出结果是32?而不是21?
- 分析:
先来看一下calloc源码流程,最后处理结果是拿到实际需要的大小进行一个十六自己对齐的运算,所以在堆区得到的大小是32个字节
- 提示:先下一份libmalloc的源码,由于配置编译运行环境比较花时间这里推荐一份已经配置好的源码:github.com/LGCooci/obj… 拿到可以运行的代码之后按上边的流程图里的方法跟下断点跑下流程,一切都会清晰明了。