8

Swift 拾遗 - 方法

 3 years ago
source link: https://kingcos.me/posts/2020/swift_methods/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
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)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK