我们在上一篇文章中分析了
alloc
的流程,后来发现其中有一些细节没有讲清楚,这里我们再来探索一下我们之前没有探索到的地方。
这一片文章将为大家介绍一下内容:
1、alloc内容补充
2、内存对齐原则
3、结构体内存对齐练习
4、一些控制台打印小技巧
1、 alloc内容补充
1.1、 上一篇内容回顾
我们在iOS底层探索 --- OC对象原理(上)中创建了一个Person
对象,并且撰写了这样一段代码:
Person *p = [Person alloc];
我们在探索源码的时候,进入的是_objc_rootAlloc
:
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
这是我们正常的源码探索流程,并没有什么错。
1.2、 问题的发现
依然是这个地方下断点:
通过汇编代码,我发现一个问题:
image
大家看到没,马上要进入的是
objc_alloc
,并不是_objc_rootAlloc
。
这就奇怪了,按理说进入的应该是_objc_rootAlloc
才对。
1.3、源码继续探索
既然动态调试的时候,汇编代码告诉我们首先进入的是objc_alloc
,那我们就在源码中搜寻一下objc_alloc
。(cmd + shift + O
)
那我们就在objc_alloc
和_objc_rootAlloc
同时打上断点,运行看一下,哪个断点先来。(注意,测试之前先clean
,避免缓存造成影响)
image这里有一点,大家要注意,两个方法都会调用
callAlloc
,但是传入的参数不同:/******** _objc_rootAlloc ********/ id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } /******** objc_alloc ********/ // Calls [cls alloc]. id objc_alloc(Class cls) { return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/); }
可以看到首先进入的是
objc_alloc
。记下来进入callAlloc
,在该方法中打上断点:image
继续运行,你会发现,它会跳过所有
if判断
,进入到最后一行,objc_msgSend
:image
接下来才会进入我们在iOS底层探索 --- OC对象原理(上)中探索的流程。
虽然动态调试的时候,流程确实是这样的;但是问题就来了,这个流程不符合我们的尝试,因为alloc
函数里面是_objc_rootAlloc
,可是什么时候变成了objc_alloc
呢?
这个给我的第一印象是,是不是苹果在某个地方进行了方法交换?或者是HOOK
?
有了这个想法,那接下来就是去源码里面寻找了。于是我找到了这个:
继续跟进,发现是在_read_images
里面调用的:
那么我们就对
_read_images
进行搜索,在其被调用的地方,埋下断点,然后运行工程,然后发现了一些熟悉的内容:image
看到没,这是
dyld
的加载,也就是说,在dyld
加载的时候,工程就对alloc
进行了替换,其IMP
指向了objc_alloc
;在执行完objc_alloc
之后,走的是消息发送流程objc_msgSend
。对这一块不太了解的同学,可以查阅:
iOS底层探索 --- dyld加载流程
iOS底层探索 ---Runtime(一)--- 基础知识
总结起来就是:
alloc在执行的时候,会先走一个特殊流程(objc_alloc),然后通过消息发送,进入普通的alloc流程。这里主要是因为,
alloc
是对内存的操作,苹果对于这样的操作要做一定的管控。
2、内存对齐原则
- 数据成员对齐规则:结构体(struct)(或联合(
union
))的成员,第一个数据成员放在offset
为0
的地方,以后每个数据成员存储的起始位置要从该成员大小(当前成员)
或者成员的子成员大小
(只要该成员有子成员,比如说数组
,结构体
等)的整数倍开始。(比如:int
为4字节
,则要从4
的整数倍地址开始存储。) -
结构体作为成员:如果一个结构体里面有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(比如:
struct a
里面存有struct b
,b
里面有char
,int
,double
等元素,那么b
应该从8
的整数倍开始存储。) - 收尾工作:结构体的总大小,也就是
sizeof
的结果,必须是其内部最大成员的整数倍,不足的要补齐。
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 | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
3、结构体内存对齐练习
- 练习题(1):
struct JaxStruct1 {
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
}struct1;
image
- 练习题(2):
struct JaxStruct2 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
}struct2;
image
- 练习题(3):
struct JaxStruct3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15]
int e; // 4 [16 17 18 19]
struct JaxStruct1 str; // 24 (20 21 22 23 [sizeof(struct1)]
}struct3;
image
4、一些控制台打印小技巧
我们在Xcode使用控制台打印一些数据的时候,有使用过这样一种形式的:
4.1、打印对象内存数据
x/nuf <address>
-
n
:表示要显示的内存单元的个数。
-
u
:表示一个地址单位的长度:-
b
:表示单字节 -
h
:表示双字节 -
w
:表示四字节 -
g
:表示八字节
-
-
f
:表示显示方式,可取如下值:-
x
:按十六进制格式显示变量 -
d
:按十进制格式显示变量 -
u
:按十进制格式显示无符号整形 -
o
:按八进制格式显示变量 -
t
:按二进制格式显示变量 -
a
:按十六进制格式显示变量 -
i
:指令地址格式 -
c
:按字符格式显示变量 -
f
:按浮点数格式显示变量
-
- 一般来讲,省略
f
,默认的是16进制
:
image
4.2、打印对象成员变量
- 如果成员变量是
NSString
,或者Int
等类型,使用po
执行就可以打印:
$ po <address>
- 如果想要打印出浮点数,可以这样打印:
$ p/f <address>
或者
$ e -f f -- <address>
image
网友评论