Date | Notes |
---|---|
2020-09-20 | 首次提交 |
Preface
《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。今天我们将一起简单探究 Swift 中的方法。
结构体
Swift 中的结构体是值类型,且不能像类一样支持继承,因此其结构十分简单:
struct Foo {
func foo() {
print(#function)
}
}
var f = Foo()
f.foo() // BREAKPOINT 🔴
// foo()
尝试运行后在断点处查看汇编:
demo`main:
0x100002b50 <+0>: pushq %rbp
0x100002b51 <+1>: movq %rsp, %rbp
0x100002b54 <+4>: subq $0x10, %rsp
0x100002b58 <+8>: movl %edi, -0x4(%rbp)
0x100002b5b <+11>: movq %rsi, -0x10(%rbp)
0x100002b5f <+15>: callq 0x100002c70 ; demo.Foo.init() -> demo.Foo at main.swift:4
; 调用内存地址为 0x100002b80 的方法
-> 0x100002b64 <+20>: callq 0x100002b80 ; demo.Foo.foo() -> () at main.swift:5
0x100002b69 <+25>: xorl %eax, %eax
0x100002b6b <+27>: addq $0x10, %rsp
0x100002b6f <+31>: popq %rbp
0x100002b70 <+32>: retq
如上,结构体变量的方法调用将直接在编译时刻决定函数所在地址,并进行调用。
类
Swift 中的类是引用类型,且支持单继承。但继承就会带来一个问题,多态,即父类指针指向子类对象;此时指针将无法在编译时刻决定实际调用对象的函数地址:
class Foo {
func foo1() {
print(#function)
}
func foo2() {
print(#function)
}
}
class SubFoo : Foo {
override func foo1() {
print("override \(#function)")
}
func subFoo3() {
print(#function)
}
}
var f = Foo()
f.foo1() // BREAKPOINT 🔴
f.foo2()
f = SubFoo()
f.foo1()
f.foo2()
尝试运行后在第一个断点处查看汇编:
0x100002394 <+36>: callq 0x1000026b0 ; demo.Foo.__allocating_init() -> demo.Foo at main.swift:5
0x100002399 <+41>: leaq 0x60a0(%rip), %rcx ; demo.f : demo.Foo
0x1000023a0 <+48>: xorl %r8d, %r8d
0x1000023a3 <+51>: movl %r8d, %edx
0x1000023a6 <+54>: movq %rax, 0x6093(%rip) ; demo.f : demo.Foo
-> 0x1000023ad <+61>: movq %rcx, %rdi
0x1000023b0 <+64>: leaq -0x20(%rbp), %rsi
0x1000023b4 <+68>: movl $0x20, %eax
0x1000023b9 <+73>: movq %rdx, -0x58(%rbp)
0x1000023bd <+77>: movq %rax, %rdx
0x1000023c0 <+80>: movq -0x58(%rbp), %rcx
0x1000023c4 <+84>: callq 0x100003bcc ; symbol stub for: swift_beginAccess
; (lldb) x/2xg 0x100008440 => 0x100008440: 0x0000000100617e80 0x0000000000000000
; 移动 rip + 0x6070 地址(0x100008440,即全局变量指针 `f`)对应内容(0x0000000100617e80,即 Foo 类型信息)至 rax 寄存器
0x1000023c9 <+89>: movq 0x6070(%rip), %rax ; demo.f : demo.Foo
; (lldb) register read rax => rax = 0x0000000100617e80
0x1000023d0 <+96>: movq %rax, %rcx
0x1000023d3 <+99>: movq %rcx, %rdi
; 移动 rax 寄存器内容(0x0000000100617e80,即 Foo 类型信息的内存地址)至 rbp - 0x60
0x1000023d6 <+102>: movq %rax, -0x60(%rbp)
0x1000023da <+106>: callq 0x100003bf0 ; symbol stub for: swift_retain
0x1000023df <+111>: leaq -0x20(%rbp), %rdi
0x1000023e3 <+115>: movq %rax, -0x68(%rbp)
0x1000023e7 <+119>: callq 0x100003be4 ; symbol stub for: swift_endAccess
; 移动 rbp - 0x60 至 rcx 寄存器
0x1000023ec <+124>: movq -0x60(%rbp), %rax
; 移动 rax 寄存器内的内存地址对应内容(即 Foo 类型信息)至 rcx 寄存器
0x1000023f0 <+128>: movq (%rax), %rcx
0x1000023f3 <+131>: movq %rax, %r13
; 调用 rcx + 0x50 处(Foo 类型信息偏移 0x50)函数地址,即 foo1
0x1000023f6 <+134>: callq *0x50(%rcx)
; ...
; 调用 rcx + 0x58 处(Foo 类型信息偏移 0x58)函数地址,即 foo2
0x10000244d <+221>: callq *0x58(%rcx)