这是 重识 Objective-C Runtime 系列文章的其中一篇:
对于 C 语言来说,Type 就个比较虚幻的东西,它唯一的目的便是 让编译器知道一段数据的长度,来决定如何存取 ,举个例子:
int i = 123;
char c = (char)i;
这段代码声明了一个 int 类型的变量和一个 char 类型的变量,有初始化和类型强转过程,在 x86_64 架构下,这两行代码的汇编如下:
movl $123, -4(%rbp)
movl -4(%rbp), %eax
movb %al, %cl
movb %cl, -5(%rbp)
汇编看起来混乱,但却能最真实的反映出程序的运行过程,逐行解释下:
movl $123, -4(%rbp)
move 指令就是简单的值拷贝,这条指令中出现的 movl
表示按低 32 位的长度来拷贝(也就是一个 int 的长度),与之相似的还有 8 位的 movb
(char)、16 位的 movw
(short)、64 位的 movq
(long in 64) 等; $123
即字面常量值; -4(%rbp)
代表 base pointer - 栈基地址寄存器,偏移 4 字节的位置。这个指令执行后内存如下所示:
movl -4(%rbp), %eax
将刚才 4 字节长度内存赋值给 %eax
寄存器,它是最常用的通用寄存器之一,名为 accumulator,在 64 位架构下, rax
表示这个寄存器的完全体, eax
表示它的低 32 位, ax
表示低 16 位, ah
表示第 8~16 位, al
表示最低的 8 位。这样抠门的设计一部分因为兼容历史的 32 架构,一方面也是为了更充分利用寄存器这个宝贵的资源:
movb %al, %cl
按 8 位长度 (char) 将 a 寄存器的最低 8 位移动到 c 寄存器(count register)的低 8 位。这一个指令就在做 int 到 char 的类型转换,把 123 存在寄存器的低 32 位上,再把寄存器的最低 8 位取出来,相当于把 00000000000000000000000001111011 截断成了 01111011。
movb %cl, -5(%rbp)
最后,再把刚才的结果按 8 字节的长度拷贝到 %rbp 偏移 5 的位置,完成这个 char 类型栈变量的赋值:
因此,对于 C 这种静态语言,Type 信息只用于编译器解析,除了静态检查外还影响生成:
subq $32, %rsp
的指令将 sp 向低地址移动)
然而,对于动态语言,Type 不仅在编译期起到上述作用, 还需要保留到运行时,让动态调用得以实现
,被称作 Type Encodings
,对于 Objective-C 所有 Type 的编码,都可以在 这个官方文档
中查到,里面的编码和用 @encode()
生成的一致,比如:
@encode(int) => "i"
@encode(float) => "f"
@encode(id) => "@"
@encode(SEL) => ":"
@encode(CGRect) => "{CGRect={CGPoint=dd}{CGSize=dd}}" // 64
Objective-C Class 中每个实例变量的 Type 信息全部被编码,Runtime 也提供了 ivar_getTypeEncoding
来访问。
同时,为支持消息的转发和动态调用,Objective-C Method 的 Type 信息也被以 “返回值 Type + 参数 Types” 的形式 组合编码
,还需要考虑到 self
和 _cmd
这两个隐含参数:
- (void)foo; => "v@:"
- (int)barWithBaz:(double)baz; => "iv@:d"
注:上面的方法的 Encoding 使用新的格式,旧的格式中包含调用栈大小和布局信息,如 i24@0:8i16i20
,表示调用栈帧共 24 字节大小,后面每个参数跟着的数字表示该参数在调用栈的偏移值,在 x86_64 和 ARM 成为主流后,调用的 Calling Conventions 发生巨大变化,开始借助寄存器传参,所以在“参数压栈”时代的这种编码方式逐渐被废弃。
方法的编码可以使用 method_getTypeEncoding
获取,在 Cocoa 层,被 NSMethodSignature
封装,并提供了一些便捷的解析方法。
多说一句,纯 Swift 声称自己是静态的语言,因为在编译后,任何结构都会被 Name Mangling
压缩成一个符号,比如下面的方法:
class Sark {
func foo(bar: Int) -> Int {
return bar;
}
}
经过 Name Mangling 的符号是 _TFC12TestSwift4Sark3foofT3barSi_Si
,虽然把结构都拍扁了,但该有的信息都在,Module、Class、Method、参数和返回值类型等,按照一定的格式进行了编码,感兴趣可以看 这篇文章
。