专注、坚持

Swift 拾遗 - 方法

2020.09.20 by kingcos
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)