Date | Notes |
---|---|
2020-08-16 | 首次提交 |
Preface
《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。今天我们将一起简单探究 Swift 中的闭包(Closure)。
闭包捕获
局部变量
我们将参数和返回值类型为 Int
的函数(也可称作闭包)(Int) -> Int
易名为 Closure
,并定义一个函数返回该闭包类型:
// main.swift
typealias Closure = (Int) -> Int
func foo() -> Closure {
var bar = 5
func baz(_ val: Int) -> Int {
bar += val // BREAKPOINT 🔴
return bar
}
bar = 10
return baz // BREAKPOINT 🔴
}
var f = foo()
f(1) // 11
f(2) // 13
f(3) // 16
通过 f(n)
函数多次执行的结果,我们发现局部变量 bar
似乎在每次调用后并没有被销毁,原因其实是 baz
对 bar
进行了「捕获」,接下来我们将仔细研究这一过程。
demo`foo():
0x100000d70 <+0>: pushq %rbp
0x100000d71 <+1>: movq %rsp, %rbp
0x100000d74 <+4>: subq $0x20, %rsp
0x100000d78 <+8>: leaq 0x299(%rip), %rdi
0x100000d7f <+15>: movl $0x18, %esi
0x100000d84 <+20>: movl $0x7, %edx
; ⚠️:这里调用了 swift_allocObject,即分配了堆空间
0x100000d89 <+25>: callq 0x100000f14 ; symbol stub for: swift_allocObject
; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
0x100000d8e <+30>: movq %rax, %rcx
0x100000d91 <+33>: addq $0x10, %rcx
0x100000d95 <+37>: movq %rcx, %rdx
; 初始化:将立即数 5 存储到 rax + 0x10 地址,即偏移 16 个字节
0x100000d98 <+40>: movq $0x5, 0x10(%rax)
; 二次赋值:将立即数 10 存储到 rax + 0x10 地址
0x100000da0 <+48>: movq $0xa, 0x10(%rax)
-> 0x100000da8 <+56>: movq %rax, %rdi
; 将 rax 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x8
0x100000dab <+59>: movq %rax, -0x8(%rbp)
0x100000daf <+63>: movq %rcx, -0x10(%rbp)
0x100000db3 <+67>: callq 0x100000f32 ; symbol stub for: swift_retain
0x100000db8 <+72>: movq -0x8(%rbp), %rdi
0x100000dbc <+76>: movq %rax, -0x18(%rbp)
0x100000dc0 <+80>: callq 0x100000f2c ; symbol stub for: swift_release
0x100000dc5 <+85>: movq -0x10(%rbp), %rax
; 将 rip + 0x130(0x0000000100000f00)地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
0x100000dc9 <+89>: leaq 0x130(%rip), %rax ; partial apply forwarder for baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at <compiler-generated>
; 将 rbp - 0x8 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
0x100000dd0 <+96>: movq -0x8(%rbp), %rdx
0x100000dd4 <+100>: addq $0x20, %rsp
0x100000dd8 <+104>: popq %rbp
0x100000dd9 <+105>: retq
demo`main:
0x100000bd0 <+0>: pushq %rbp
0x100000bd1 <+1>: movq %rsp, %rbp
0x100000bd4 <+4>: pushq %r13
0x100000bd6 <+6>: subq $0xc8, %rsp
0x100000bdd <+13>: movl %edi, -0x54(%rbp)
0x100000be0 <+16>: movq %rsi, -0x60(%rbp)
0x100000be4 <+20>: callq 0x100000d70 ; demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:5
; 函数调用后将把返回值(即 0x0000000100000f00,包装 baz 的函数的地址)存储到 rax 寄存器
0x100000be9 <+25>: leaq 0x1450(%rip), %rcx ; demo.f : (Swift.Int) -> Swift.Int
0x100000bf0 <+32>: xorl %edi, %edi
0x100000bf2 <+34>: movl %edi, %esi
; 将 rax 寄存器内容(即包装 baz 的函数的地址)移动到 rip + 0x1445(0x100002040)
0x100000bf4 <+36>: movq %rax, 0x1445(%rip) ; demo.f : (Swift.Int) -> Swift.Int
; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)移动到 rip + 0x1446(0x100002048)
0x100000bfb <+43>: movq %rdx, 0x1446(%rip) ; demo.f : (Swift.Int) -> Swift.Int + 8
-> 0x100000c02 <+50>: movq %rcx, %rdi
0x100000c05 <+53>: leaq -0x20(%rbp), %rax
0x100000c09 <+57>: movq %rsi, -0x68(%rbp)
0x100000c0d <+61>: movq %rax, %rsi
0x100000c10 <+64>: movl $0x20, %edx
0x100000c15 <+69>: movq -0x68(%rbp), %rcx
0x100000c19 <+73>: callq 0x100000f1a ; symbol stub for: swift_beginAccess
; 将 rip + 0x141b(0x100002040)地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
0x100000c1e <+78>: movq 0x141b(%rip), %rax ; demo.f : (Swift.Int) -> Swift.Int
; 将 rip + 0x141c(0x100002048)地址内容(即封装了 bar 的堆空间地址)移动到 rcx 寄存器
0x100000c25 <+85>: movq 0x141c(%rip), %rcx ; demo.f : (Swift.Int) -> Swift.Int + 8
0x100000c2c <+92>: movq %rcx, %rdi
; 将 rax 寄存器内容(即包装 baz 的函数的地址)移动到 rbp - 0x70
0x100000c2f <+95>: movq %rax, -0x70(%rbp)
; 将 rcx 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x78
0x100000c33 <+99>: movq %rcx, -0x78(%rbp)
0x100000c37 <+103>: callq 0x100000f32 ; symbol stub for: swift_retain
0x100000c3c <+108>: leaq -0x20(%rbp), %rdi
0x100000c40 <+112>: movq %rax, -0x80(%rbp)
0x100000c44 <+116>: callq 0x100000f26 ; symbol stub for: swift_endAccess
; 将立即数 1(f(1) 中的参数)移动到 edi 寄存器
0x100000c49 <+121>: movl $0x1, %edi
; 将 rbp - 0x78 地址内容(即封装了 bar 的堆空间地址)移动到 r13 寄存器
0x100000c4e <+126>: movq -0x78(%rbp), %r13
; 将 rbp - 0x70 地址内容(即包装 baz 的函数的地址)移动到 rax 寄存器
0x100000c52 <+130>: movq -0x70(%rbp), %rax
; 函数调用,使用 si 进入
0x100000c56 <+134>: callq *%rax
0x100000c58 <+136>: movq -0x78(%rbp), %rdi
0x100000c5c <+140>: movq %rax, -0x88(%rbp)
0x100000c63 <+147>: callq 0x100000f2c ; symbol stub for: swift_release
0x100000c68 <+152>: leaq 0x13d1(%rip), %rax ; demo.f : (Swift.Int) -> Swift.Int
0x100000c6f <+159>: xorl %r8d, %r8d
0x100000c72 <+162>: movl %r8d, %ecx
0x100000c75 <+165>: movq %rax, %rdi
0x100000c78 <+168>: leaq -0x38(%rbp), %rsi
0x100000c7c <+172>: movl $0x20, %edx
0x100000c81 <+177>: callq 0x100000f1a ; symbol stub for: swift_beginAccess
0x100000c86 <+182>: movq 0x13b3(%rip), %rax ; demo.f : (Swift.Int) -> Swift.Int
0x100000c8d <+189>: movq 0x13b4(%rip), %rcx ; demo.f : (Swift.Int) -> Swift.Int + 8
0x100000c94 <+196>: movq %rcx, %rdi
0x100000c97 <+199>: movq %rax, -0x90(%rbp)
0x100000c9e <+206>: movq %rcx, -0x98(%rbp)
0x100000ca5 <+213>: callq 0x100000f32 ; symbol stub for: swift_retain
0x100000caa <+218>: leaq -0x38(%rbp), %rdi
0x100000cae <+222>: movq %rax, -0xa0(%rbp)
0x100000cb5 <+229>: callq 0x100000f26 ; symbol stub for: swift_endAccess
0x100000cba <+234>: movl $0x2, %edi
0x100000cbf <+239>: movq -0x98(%rbp), %r13
0x100000cc6 <+246>: movq -0x90(%rbp), %rax
0x100000ccd <+253>: callq *%rax
0x100000ccf <+255>: movq -0x98(%rbp), %rdi
0x100000cd6 <+262>: movq %rax, -0xa8(%rbp)
0x100000cdd <+269>: callq 0x100000f2c ; symbol stub for: swift_release
0x100000ce2 <+274>: leaq 0x1357(%rip), %rax ; demo.f : (Swift.Int) -> Swift.Int
0x100000ce9 <+281>: xorl %r8d, %r8d
0x100000cec <+284>: movl %r8d, %ecx
0x100000cef <+287>: movq %rax, %rdi
0x100000cf2 <+290>: leaq -0x50(%rbp), %rsi
0x100000cf6 <+294>: movl $0x20, %edx
0x100000cfb <+299>: callq 0x100000f1a ; symbol stub for: swift_beginAccess
0x100000d00 <+304>: movq 0x1339(%rip), %rax ; demo.f : (Swift.Int) -> Swift.Int
0x100000d07 <+311>: movq 0x133a(%rip), %rcx ; demo.f : (Swift.Int) -> Swift.Int + 8
0x100000d0e <+318>: movq %rcx, %rdi
0x100000d11 <+321>: movq %rax, -0xb0(%rbp)
0x100000d18 <+328>: movq %rcx, -0xb8(%rbp)
0x100000d1f <+335>: callq 0x100000f32 ; symbol stub for: swift_retain
0x100000d24 <+340>: leaq -0x50(%rbp), %rdi
0x100000d28 <+344>: movq %rax, -0xc0(%rbp)
0x100000d2f <+351>: callq 0x100000f26 ; symbol stub for: swift_endAccess
0x100000d34 <+356>: movl $0x3, %edi
0x100000d39 <+361>: movq -0xb8(%rbp), %r13
0x100000d40 <+368>: movq -0xb0(%rbp), %rax
0x100000d47 <+375>: callq *%rax
0x100000d49 <+377>: movq -0xb8(%rbp), %rdi
0x100000d50 <+384>: movq %rax, -0xc8(%rbp)
0x100000d57 <+391>: callq 0x100000f2c ; symbol stub for: swift_release
0x100000d5c <+396>: xorl %eax, %eax
0x100000d5e <+398>: addq $0xc8, %rsp
0x100000d65 <+405>: popq %r13
0x100000d67 <+407>: popq %rbp
0x100000d68 <+408>: retq
demo`partial apply for baz #1 (_:) in foo():
-> 0x100000f00 <+0>: pushq %rbp
0x100000f01 <+1>: movq %rsp, %rbp
; rdi 未改变,即第一个参数(1)
; 将 r13 寄存器内容(即封装了 bar 的堆空间地址)移动到 rsi 寄存器(即第二个参数)
0x100000f04 <+4>: movq %r13, %rsi
0x100000f07 <+7>: popq %rbp
; 继续 si
0x100000f08 <+8>: jmp 0x100000e00 ; baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:8
demo`baz #1 (_:) in foo():
-> 0x100000e00 <+0>: pushq %rbp
0x100000e01 <+1>: movq %rsp, %rbp
0x100000e04 <+4>: subq $0x90, %rsp
0x100000e0b <+11>: xorl %eax, %eax
0x100000e0d <+13>: movl %eax, %ecx
0x100000e0f <+15>: xorl %eax, %eax
0x100000e11 <+17>: leaq -0x8(%rbp), %rdx
; 将 rdi 寄存器内容(即 1)移动到 rbp - 0x48 地址
0x100000e15 <+21>: movq %rdi, -0x48(%rbp)
0x100000e19 <+25>: movq %rdx, %rdi
; 将 rsi 寄存器内容(即封装了 bar 的堆空间地址)移动到 rbp - 0x50 地址
0x100000e1c <+28>: movq %rsi, -0x50(%rbp)
0x100000e20 <+32>: movl %eax, %esi
0x100000e22 <+34>: movl $0x8, %edx
0x100000e27 <+39>: movq %rdx, -0x58(%rbp)
0x100000e2b <+43>: movq %rcx, -0x60(%rbp)
0x100000e2f <+47>: movl %eax, -0x64(%rbp)
0x100000e32 <+50>: callq 0x100000f0e ; symbol stub for: memset
0x100000e37 <+55>: leaq -0x10(%rbp), %rcx
0x100000e3b <+59>: movq %rcx, %rdi
0x100000e3e <+62>: movl -0x64(%rbp), %esi
0x100000e41 <+65>: movq -0x58(%rbp), %rdx
0x100000e45 <+69>: callq 0x100000f0e ; symbol stub for: memset
; 将 rbp - 0x48 地址内容(即 1)移动到 rcx 寄存器
0x100000e4a <+74>: movq -0x48(%rbp), %rcx
0x100000e4e <+78>: movq %rcx, -0x8(%rbp)
; 将 rbp - 0x50 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
0x100000e52 <+82>: movq -0x50(%rbp), %rdx
; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)偏移 0x10(即 bar 的堆空间地址),结果存储在 rdx 寄存器
0x100000e56 <+86>: addq $0x10, %rdx
0x100000e5d <+93>: movq %rdx, -0x10(%rbp)
0x100000e61 <+97>: movq %rdx, %rdi
0x100000e64 <+100>: leaq -0x28(%rbp), %rsi
0x100000e68 <+104>: movl $0x21, %r8d
; 将 rdx 寄存器内容(即 bar 的堆空间地址)移动到 rbp - 0x70 地址
0x100000e6e <+110>: movq %rdx, -0x70(%rbp)
0x100000e72 <+114>: movq %r8, %rdx
0x100000e75 <+117>: movq -0x60(%rbp), %rcx
0x100000e79 <+121>: callq 0x100000f1a ; symbol stub for: swift_beginAccess
0x100000e7e <+126>: movq -0x48(%rbp), %rcx
; 再次将 rbp - 0x50 地址内容(即封装了 bar 的堆空间地址)移动到 rdx 寄存器
0x100000e82 <+130>: movq -0x50(%rbp), %rdx
; 将 rdx 寄存器内容(即封装了 bar 的堆空间地址)偏移 0x10(即 bar 的堆空间地址)与 rcx 寄存器内容(即 1)相加,结果存储在 rcx 寄存器
0x100000e86 <+134>: addq 0x10(%rdx), %rcx
0x100000e8a <+138>: seto %r9b
0x100000e8e <+142>: testb $0x1, %r9b
0x100000e92 <+146>: movq %rcx, -0x78(%rbp)
0x100000e96 <+150>: jne 0x100000ef0 ; <+240> at main.swift:9:13
0x100000e98 <+152>: movq -0x70(%rbp), %rax
0x100000e9c <+156>: movq -0x78(%rbp), %rcx
0x100000ea0 <+160>: movq %rcx, (%rax)
0x100000ea3 <+163>: leaq -0x28(%rbp), %rdi
0x100000ea7 <+167>: callq 0x100000f26 ; symbol stub for: swift_endAccess
0x100000eac <+172>: xorl %edx, %edx
0x100000eae <+174>: movl %edx, %ecx
0x100000eb0 <+176>: leaq -0x40(%rbp), %rax
0x100000eb4 <+180>: movl $0x20, %edx
0x100000eb9 <+185>: movq -0x70(%rbp), %rdi
0x100000ebd <+189>: movq %rax, %rsi
0x100000ec0 <+192>: movq %rax, -0x80(%rbp)
0x100000ec4 <+196>: callq 0x100000f1a ; symbol stub for: swift_beginAccess
; 将 rbp - 0x70 地址内容(即 bar 的堆空间地址)移动到 rax 寄存器
0x100000ec9 <+201>: movq -0x70(%rbp), %rax
; 将 rax 寄存器的值(即 0x000000000000000b)移动到 rax 寄存器
0x100000ecd <+205>: movq (%rax), %rax
0x100000ed0 <+208>: movq -0x80(%rbp), %rdi
; 将 rax 寄存器内容(0x000000000000000b)移动到 rbp - 0x88 地址
0x100000ed4 <+212>: movq %rax, -0x88(%rbp)
0x100000edb <+219>: callq 0x100000f26 ; symbol stub for: swift_endAccess
; 将 rbp - 0x88 地址内容(0x000000000000000b)移动到 rax 寄存器
0x100000ee0 <+224>: movq -0x88(%rbp), %rax
0x100000ee7 <+231>: addq $0x90, %rsp
0x100000eee <+238>: popq %rbp
0x100000eef <+239>: retq
0x100000ef0 <+240>: ud2
由以上汇编代码我们可以得出,当闭包内部使用了外部局部变量时,将自动分配堆空间进行捕获;在后续调用闭包时,将自动将创建堆空间地址作为第二个参数传递到闭包中并使用;因此即使局部变量所在的栈空间被回收,堆空间仍将保留一份。这里的堆空间布局类似于 class
类型实例变量的内存布局。
另外需要注意的是,swift_allocObject
在 foo
中被调用,因此如果我们声明一个新变量例如 var f2 = foo()
,将会创建另外一份新的堆空间,f(1)
与 f2(1)
的调用将产生隔离。
那么当同时存在两个或多个函数时,局部变量将被分配一次还是多次堆空间呢?
// main.swift
typealias Closure = (Int) -> Int
func foo() -> (Closure, Closure) {
var i = 5
var j = 10
func a(_ val: Int) -> Int {
i += val
j += val
return i + j
}
func b(_ val: Int) -> Int {
i -= val
j -= val
return i + j
}
return (a, b) // BREAKPOINT 🔴
}
var (a, b) = foo()
a(1) // 17
b(2) // 13
a(3) // 19
b(4) // 11
转换为汇编如下:
demo`foo():
0x100001880 <+0>: pushq %rbp
0x100001881 <+1>: movq %rsp, %rbp
0x100001884 <+4>: subq $0x70, %rsp
0x100001888 <+8>: leaq 0x789(%rip), %rax
0x10000188f <+15>: movl $0x18, %ecx
0x100001894 <+20>: movl $0x7, %edx
0x100001899 <+25>: movq %rax, %rdi
0x10000189c <+28>: movq %rcx, %rsi
0x10000189f <+31>: movq %rdx, -0x8(%rbp)
0x1000018a3 <+35>: movq %rax, -0x10(%rbp)
0x1000018a7 <+39>: movq %rcx, -0x18(%rbp)
; ⚠️:这里调用了 swift_allocObject,即分配了堆空间
0x1000018ab <+43>: callq 0x100001ed8 ; symbol stub for: swift_allocObject
; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
0x1000018b0 <+48>: movq %rax, %rcx
0x1000018b3 <+51>: addq $0x10, %rcx
0x1000018b7 <+55>: movq %rcx, %rdx
; i 初始化:将立即数 5 存储到 rax + 0x10 地址,即偏移 16 个字节
0x1000018ba <+58>: movq $0x5, 0x10(%rax)
0x1000018c2 <+66>: movq -0x10(%rbp), %rdi
0x1000018c6 <+70>: movq -0x18(%rbp), %rsi
0x1000018ca <+74>: movq -0x8(%rbp), %rdx
; 将 rax 寄存器内容(即封装了 i 的堆空间地址)移动到 rbp - 0x20
0x1000018ce <+78>: movq %rax, -0x20(%rbp)
0x1000018d2 <+82>: movq %rcx, -0x28(%rbp)
; ⚠️:这里又一次调用了 swift_allocObject,即再次分配了堆空间
0x1000018d6 <+86>: callq 0x100001ed8 ; symbol stub for: swift_allocObject
; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
0x1000018db <+91>: movq %rax, %rcx
0x1000018de <+94>: addq $0x10, %rcx
0x1000018e2 <+98>: movq %rcx, %rdx
; j 初始化:将立即数 10 存储到 rax + 0x10 地址,即偏移 16 个字节
0x1000018e5 <+101>: movq $0xa, 0x10(%rax)
0x1000018ed <+109>: movq -0x20(%rbp), %rdi
; 将 rax 寄存器内容(即封装了 j 的堆空间地址)移动到 rbp - 0x30
0x1000018f1 <+113>: movq %rax, -0x30(%rbp)
0x1000018f5 <+117>: movq %rcx, -0x38(%rbp)
-> 0x1000018f9 <+121>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x1000018fe <+126>: movq -0x30(%rbp), %rdi
0x100001902 <+130>: movq %rax, -0x40(%rbp)
0x100001906 <+134>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x10000190b <+139>: leaq 0x72e(%rip), %rdi
0x100001912 <+146>: movl $0x20, %ecx
0x100001917 <+151>: movq %rcx, %rsi
0x10000191a <+154>: movq -0x8(%rbp), %rdx
0x10000191e <+158>: movq %rax, -0x48(%rbp)
0x100001922 <+162>: movq %rcx, -0x50(%rbp)
; ⚠️:这里又一次调用了 swift_allocObject,即分配了堆空间
0x100001926 <+166>: callq 0x100001ed8 ; symbol stub for: swift_allocObject
; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
; 将 rbp - 0x20 地址内容(即封装了 i 的堆空间地址)移动到 rcx 寄存器
0x10000192b <+171>: movq -0x20(%rbp), %rcx
; 将 rcx 寄存器内容(即封装了 i 的堆空间地址)移动到 rax + 0x10
0x10000192f <+175>: movq %rcx, 0x10(%rax)
; 将 rbp - 0x30 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器
0x100001933 <+179>: movq -0x30(%rbp), %rdx
; 将 rdx 寄存器内容(即封装了 j 的堆空间地址)移动到 rax + 0x18
0x100001937 <+183>: movq %rdx, 0x18(%rax)
0x10000193b <+187>: movq %rcx, %rdi
; 将 rax 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x58
0x10000193e <+190>: movq %rax, -0x58(%rbp)
0x100001942 <+194>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x100001947 <+199>: movq -0x30(%rbp), %rdi
0x10000194b <+203>: movq %rax, -0x60(%rbp)
0x10000194f <+207>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x100001954 <+212>: leaq 0x70d(%rip), %rdi
0x10000195b <+219>: movq -0x50(%rbp), %rsi
0x10000195f <+223>: movq -0x8(%rbp), %rdx
0x100001963 <+227>: movq %rax, -0x68(%rbp)
; ⚠️:这里又一次调用了 swift_allocObject,即分配了堆空间
0x100001967 <+231>: callq 0x100001ed8 ; symbol stub for: swift_allocObject
; alloc 函数调用后将返回堆空间首地址,并存储在 rax 寄存器
; 将 rbp - 0x20 地址内容(即封装了 i 的堆空间地址)移动到 rcx 寄存器
0x10000196c <+236>: movq -0x20(%rbp), %rcx
; 将 rcx 寄存器内容(即封装了 i 的堆空间地址)移动到 rax + 0x10
0x100001970 <+240>: movq %rcx, 0x10(%rax)
; 将 rbp - 0x30 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器
0x100001974 <+244>: movq -0x30(%rbp), %rdx
; 将 rdx 寄存器内容(即封装了 j 的堆空间地址)移动到 rax + 0x18
0x100001978 <+248>: movq %rdx, 0x18(%rax)
0x10000197c <+252>: movq %rdx, %rdi
; 将 rax 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x70
0x10000197f <+255>: movq %rax, -0x70(%rbp)
0x100001983 <+259>: callq 0x100001ef0 ; symbol stub for: swift_release
0x100001988 <+264>: movq -0x20(%rbp), %rdi
0x10000198c <+268>: callq 0x100001ef0 ; symbol stub for: swift_release
0x100001991 <+273>: movq -0x28(%rbp), %rax
0x100001995 <+277>: movq -0x38(%rbp), %rcx
; 将 rip + 0x2a0(0x100001C40)地址内容(即包装 a 的函数的地址)移动到 rax 寄存器
0x100001999 <+281>: leaq 0x2a0(%rip), %rax ; partial apply forwarder for a #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at <compiler-generated>
; 将 rip + 0x519(0x100001EC0)地址内容(即包装 b 的函数的地址)移动到 rcx 寄存器
0x1000019a0 <+288>: leaq 0x519(%rip), %rcx ; partial apply forwarder for b #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at <compiler-generated>
; 将 rbp - 0x58 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rdx 寄存器
; (lldb) register read rdx => rdx = 0x00000001007bdcc0
; (lldb) x/4xg 0x00000001007bdcc0
; 0x1007bdcc0: 0x0000000100002040 0x0000000000000002
; 0x1007bdcd0: 0x00000001007bdbf0 0x00000001007bdca0(i、j)
0x1000019a7 <+295>: movq -0x58(%rbp), %rdx
; 将 rbp - 0x70 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r8 寄存器
; (lldb) register read r8 => r8 = 0x0000000100404120
; (lldb) x/4xg 0x0000000100404120
; 0x100404120: 0x0000000100002068 0x0000000000000002
; 0x100404130: 0x00000001007bdbf0 0x00000001007bdca0(i、j)
0x1000019ab <+299>: movq -0x70(%rbp), %r8
0x1000019af <+303>: addq $0x70, %rsp
0x1000019b3 <+307>: popq %rbp
0x1000019b4 <+308>: retq
demo`main:
0x100001650 <+0>: pushq %rbp
0x100001651 <+1>: movq %rsp, %rbp
0x100001654 <+4>: pushq %r13
0x100001656 <+6>: subq $0xf8, %rsp
0x10000165d <+13>: movl %edi, -0x6c(%rbp)
0x100001660 <+16>: movq %rsi, -0x78(%rbp)
0x100001664 <+20>: callq 0x100001880 ; demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at main.swift:5
; 函数调用后将把返回值存储到 rax 寄存器
0x100001669 <+25>: leaq 0x19d0(%rip), %rsi ; demo.a : (Swift.Int) -> Swift.Int
0x100001670 <+32>: xorl %edi, %edi
0x100001672 <+34>: movl %edi, %r9d
; 搬运返回值
0x100001675 <+37>: movq %rax, 0x19c4(%rip) ; demo.a : (Swift.Int) -> Swift.Int
; 将 rdx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rip + 0x19c5(0x100003048)
0x10000167c <+44>: movq %rdx, 0x19c5(%rip) ; demo.a : (Swift.Int) -> Swift.Int + 8
0x100001683 <+51>: movq %rcx, 0x19c6(%rip) ; demo.b : (Swift.Int) -> Swift.Int
; 将 r8 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rip + 0x19c7(0x100003058)
0x10000168a <+58>: movq %r8, 0x19c7(%rip) ; demo.b : (Swift.Int) -> Swift.Int + 8
-> 0x100001691 <+65>: movq %rsi, %rdi
0x100001694 <+68>: leaq -0x20(%rbp), %rsi
0x100001698 <+72>: movl $0x20, %edx
0x10000169d <+77>: movq %r9, %rcx
0x1000016a0 <+80>: callq 0x100001ede ; symbol stub for: swift_beginAccess
0x1000016a5 <+85>: movq 0x1994(%rip), %rax ; demo.a : (Swift.Int) -> Swift.Int
; 将 rip + 0x1995 地址内容(0x100003048,即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rcx 寄存器
0x1000016ac <+92>: movq 0x1995(%rip), %rcx ; demo.a : (Swift.Int) -> Swift.Int + 8
0x1000016b3 <+99>: movq %rcx, %rdi
0x1000016b6 <+102>: movq %rax, -0x80(%rbp)
; 将 rcx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0x88
0x1000016ba <+106>: movq %rcx, -0x88(%rbp)
0x1000016c1 <+113>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x1000016c6 <+118>: leaq -0x20(%rbp), %rdi
0x1000016ca <+122>: movq %rax, -0x90(%rbp)
0x1000016d1 <+129>: callq 0x100001eea ; symbol stub for: swift_endAccess
; 将立即数 1(a(1) 中的参数)移动到 edi 寄存器
0x1000016d6 <+134>: movl $0x1, %edi
; 将 rbp - 0x88 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r13 寄存器
0x1000016db <+139>: movq -0x88(%rbp), %r13
0x1000016e2 <+146>: movq -0x80(%rbp), %rax
; a(1) 函数调用,使用 si 进入
0x1000016e6 <+150>: callq *%rax
0x1000016e8 <+152>: movq -0x88(%rbp), %rdi
0x1000016ef <+159>: movq %rax, -0x98(%rbp)
0x1000016f6 <+166>: callq 0x100001ef0 ; symbol stub for: swift_release
0x1000016fb <+171>: leaq 0x194e(%rip), %rax ; demo.b : (Swift.Int) -> Swift.Int
0x100001702 <+178>: xorl %r10d, %r10d
0x100001705 <+181>: movl %r10d, %ecx
0x100001708 <+184>: movq %rax, %rdi
0x10000170b <+187>: leaq -0x38(%rbp), %rsi
0x10000170f <+191>: movl $0x20, %edx
0x100001714 <+196>: callq 0x100001ede ; symbol stub for: swift_beginAccess
0x100001719 <+201>: movq 0x1930(%rip), %rax ; demo.b : (Swift.Int) -> Swift.Int
; 将 rip + 0x1931 地址内容(0x100003058,即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rcx 寄存器
0x100001720 <+208>: movq 0x1931(%rip), %rcx ; demo.b : (Swift.Int) -> Swift.Int + 8
0x100001727 <+215>: movq %rcx, %rdi
0x10000172a <+218>: movq %rax, -0xa0(%rbp)
; 将 rcx 寄存器内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 rbp - 0xa8
0x100001731 <+225>: movq %rcx, -0xa8(%rbp)
0x100001738 <+232>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x10000173d <+237>: leaq -0x38(%rbp), %rdi
0x100001741 <+241>: movq %rax, -0xb0(%rbp)
0x100001748 <+248>: callq 0x100001eea ; symbol stub for: swift_endAccess
; 将立即数 2(b(2) 中的参数)移动到 edi 寄存器
0x10000174d <+253>: movl $0x2, %edi
; 将 rbp - 0xa8 地址内容(即可通过偏移找到封装 i 和 j 的堆空间地址)移动到 r13 寄存器
0x100001752 <+258>: movq -0xa8(%rbp), %r13
0x100001759 <+265>: movq -0xa0(%rbp), %rax
; b(2) 函数调用,使用 si 进入
0x100001760 <+272>: callq *%rax
0x100001762 <+274>: movq -0xa8(%rbp), %rdi
0x100001769 <+281>: movq %rax, -0xb8(%rbp)
0x100001770 <+288>: callq 0x100001ef0 ; symbol stub for: swift_release
0x100001775 <+293>: leaq 0x18c4(%rip), %rax ; demo.a : (Swift.Int) -> Swift.Int
0x10000177c <+300>: xorl %r10d, %r10d
0x10000177f <+303>: movl %r10d, %ecx
0x100001782 <+306>: movq %rax, %rdi
0x100001785 <+309>: leaq -0x50(%rbp), %rsi
0x100001789 <+313>: movl $0x20, %edx
0x10000178e <+318>: callq 0x100001ede ; symbol stub for: swift_beginAccess
0x100001793 <+323>: movq 0x18a6(%rip), %rax ; demo.a : (Swift.Int) -> Swift.Int
0x10000179a <+330>: movq 0x18a7(%rip), %rcx ; demo.a : (Swift.Int) -> Swift.Int + 8
0x1000017a1 <+337>: movq %rcx, %rdi
0x1000017a4 <+340>: movq %rax, -0xc0(%rbp)
0x1000017ab <+347>: movq %rcx, -0xc8(%rbp)
0x1000017b2 <+354>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x1000017b7 <+359>: leaq -0x50(%rbp), %rdi
0x1000017bb <+363>: movq %rax, -0xd0(%rbp)
0x1000017c2 <+370>: callq 0x100001eea ; symbol stub for: swift_endAccess
0x1000017c7 <+375>: movl $0x3, %edi
0x1000017cc <+380>: movq -0xc8(%rbp), %r13
0x1000017d3 <+387>: movq -0xc0(%rbp), %rax
0x1000017da <+394>: callq *%rax
0x1000017dc <+396>: movq -0xc8(%rbp), %rdi
0x1000017e3 <+403>: movq %rax, -0xd8(%rbp)
0x1000017ea <+410>: callq 0x100001ef0 ; symbol stub for: swift_release
0x1000017ef <+415>: leaq 0x185a(%rip), %rax ; demo.b : (Swift.Int) -> Swift.Int
0x1000017f6 <+422>: xorl %r10d, %r10d
0x1000017f9 <+425>: movl %r10d, %ecx
0x1000017fc <+428>: movq %rax, %rdi
0x1000017ff <+431>: leaq -0x68(%rbp), %rsi
0x100001803 <+435>: movl $0x20, %edx
0x100001808 <+440>: callq 0x100001ede ; symbol stub for: swift_beginAccess
0x10000180d <+445>: movq 0x183c(%rip), %rax ; demo.b : (Swift.Int) -> Swift.Int
0x100001814 <+452>: movq 0x183d(%rip), %rcx ; demo.b : (Swift.Int) -> Swift.Int + 8
0x10000181b <+459>: movq %rcx, %rdi
0x10000181e <+462>: movq %rax, -0xe0(%rbp)
0x100001825 <+469>: movq %rcx, -0xe8(%rbp)
0x10000182c <+476>: callq 0x100001ef6 ; symbol stub for: swift_retain
0x100001831 <+481>: leaq -0x68(%rbp), %rdi
0x100001835 <+485>: movq %rax, -0xf0(%rbp)
0x10000183c <+492>: callq 0x100001eea ; symbol stub for: swift_endAccess
0x100001841 <+497>: movl $0x4, %edi
0x100001846 <+502>: movq -0xe8(%rbp), %r13
0x10000184d <+509>: movq -0xe0(%rbp), %rax
0x100001854 <+516>: callq *%rax
0x100001856 <+518>: movq -0xe8(%rbp), %rdi
0x10000185d <+525>: movq %rax, -0xf8(%rbp)
0x100001864 <+532>: callq 0x100001ef0 ; symbol stub for: swift_release
0x100001869 <+537>: xorl %eax, %eax
0x10000186b <+539>: addq $0xf8, %rsp
0x100001872 <+546>: popq %r13
0x100001874 <+548>: popq %rbp
0x100001875 <+549>: retq
demo`partial apply for a #1 (_:) in foo():
-> 0x100001c40 <+0>: pushq %rbp
0x100001c41 <+1>: movq %rsp, %rbp
; rdi 未改变,即第一个参数(1)
; 将 r13 + 0x10 地址内容(即封装了 i 的堆空间地址)移动到 rsi 寄存器(即第二个参数)
0x100001c44 <+4>: movq 0x10(%r13), %rsi
; 将 r13 + 0x18 地址内容(即封装了 j 的堆空间地址)移动到 rdx 寄存器(即第三个参数)
0x100001c48 <+8>: movq 0x18(%r13), %rdx
0x100001c4c <+12>: popq %rbp
0x100001c4d <+13>: jmp 0x1000019e0 ; a #1 (Swift.Int) -> Swift.Int in demo.foo() -> ((Swift.Int) -> Swift.Int, (Swift.Int) -> Swift.Int) at main.swift:9
如上,首先每个需要被分配到堆上的局部变量(如 i
和 j
)均会各自创建一份独立的堆空间,并将这些局部变量存储其中,保证其作用域超过函数而不被立即释放;之后会根据函数(闭包)个数创建相应个数的堆空间,并将前者的堆空间地址放置在新的堆空间中,注意编译器虽然根据每个函数都创建了对应的堆空间,但堆空间的内容却是一致的,均引用了相同的局部变量堆空间;因此这些函数实际调用时,将访问到同一片内存区域的局部变量值。
全局变量
这次我们将 bar
声明为全局变量:
// main.swift
typealias Closure = (Int) -> Int
var bar = 5 // BREAKPOINT 🔴
func foo() -> Closure {
func baz(_ val: Int) -> Int {
bar += val // BREAKPOINT 🔴
return bar
}
bar = 10
return baz // BREAKPOINT 🔴
}
var f = foo()
f(1) // 11
f(2) // 13
f(3) // 16
此时我们可以从主函数的汇编中看出,bar
被移动到了通过 %rip
寄存器偏移的地址,而 %rip
寄存器固定存储下一条指令的位置,因此 bar
确实属于全局变量。我们也可以通过 (lldb) po withUnsafePointer(to: &bar) { print("\($0)") }
验证其内存地址位于 0x0000000100002050
:
demo`main:
0x100000ae0 <+0>: pushq %rbp
0x100000ae1 <+1>: movq %rsp, %rbp
0x100000ae4 <+4>: pushq %r13
0x100000ae6 <+6>: subq $0x118, %rsp ; imm = 0x118
; 移动立即数 5 到 rip + 0x1558 地址(0x100002050)
-> 0x100000aed <+13>: movq $0x5, 0x1558(%rip) ; _dyld_private + 4
0x100000af8 <+24>: movl %edi, -0x6c(%rbp)
; ...
而当断点执行到 foo
函数内部时,此时与局部变量不同的是,并没有存在 swift_allocObject
的符号桩:
demo`foo():
0x100000d90 <+0>: pushq %rbp
0x100000d91 <+1>: movq %rsp, %rbp
0x100000d94 <+4>: subq $0x30, %rsp
0x100000d98 <+8>: leaq 0x12b1(%rip), %rdi ; demo.bar : Swift.Int
0x100000d9f <+15>: xorl %eax, %eax
0x100000da1 <+17>: movl %eax, %ecx
0x100000da3 <+19>: leaq -0x18(%rbp), %rdx
0x100000da7 <+23>: movl $0x21, %esi
0x100000dac <+28>: movq %rsi, -0x20(%rbp)
0x100000db0 <+32>: movq %rdx, %rsi
0x100000db3 <+35>: movq -0x20(%rbp), %r8
0x100000db7 <+39>: movq %rdx, -0x28(%rbp)
0x100000dbb <+43>: movq %r8, %rdx
0x100000dbe <+46>: movq %rcx, -0x30(%rbp)
0x100000dc2 <+50>: callq 0x100000f14 ; symbol stub for: swift_beginAccess
0x100000dc7 <+55>: movq $0xa, 0x127e(%rip) ; _dyld_private + 4
0x100000dd2 <+66>: movq -0x28(%rbp), %rdi
0x100000dd6 <+70>: callq 0x100000f20 ; symbol stub for: swift_endAccess
-> 0x100000ddb <+75>: leaq 0xe(%rip), %rax ; baz #1 (Swift.Int) -> Swift.Int in demo.foo() -> (Swift.Int) -> Swift.Int at main.swift:9
0x100000de2 <+82>: movq -0x30(%rbp), %rdx
0x100000de6 <+86>: addq $0x30, %rsp
0x100000dea <+90>: popq %rbp
0x100000deb <+91>: retq
而最终在 baz
函数内部:
demo`baz #1 (_:) in foo():
0x100000df0 <+0>: pushq %rbp
0x100000df1 <+1>: movq %rsp, %rbp
0x100000df4 <+4>: subq $0x70, %rsp
0x100000df8 <+8>: leaq 0x1251(%rip), %rax ; demo.bar : Swift.Int
0x100000dff <+15>: xorl %ecx, %ecx
0x100000e01 <+17>: xorl %esi, %esi
0x100000e03 <+19>: leaq -0x8(%rbp), %rdx
; 将 rdi 寄存器内容(1)移动到 rbp - 0x40 地址
0x100000e07 <+23>: movq %rdi, -0x40(%rbp)
0x100000e0b <+27>: movq %rdx, %rdi
0x100000e0e <+30>: movl $0x8, %edx
0x100000e13 <+35>: movq %rax, -0x48(%rbp)
0x100000e17 <+39>: movq %rcx, -0x50(%rbp)
0x100000e1b <+43>: callq 0x100000f0e ; symbol stub for: memset
; 将 rbp - 0x40 地址内容(1)移动到 rax 寄存器
0x100000e20 <+48>: movq -0x40(%rbp), %rax
0x100000e24 <+52>: movq %rax, -0x8(%rbp)
0x100000e28 <+56>: movq -0x48(%rbp), %rdi
-> 0x100000e2c <+60>: leaq -0x20(%rbp), %rsi
0x100000e30 <+64>: movl $0x21, %edx
0x100000e35 <+69>: movq -0x50(%rbp), %rcx
0x100000e39 <+73>: callq 0x100000f14 ; symbol stub for: swift_beginAccess
; 将 rbp - 0x40 地址内容(1)移动到 rax 寄存器
0x100000e3e <+78>: movq -0x40(%rbp), %rax
; 将 rip + 0x1207(0x100002050)地址内容(bar)与 rax 寄存器内容(即 1)相加,结果存储在 rip + 0x1207 地址
0x100000e42 <+82>: addq 0x1207(%rip), %rax ; demo.bar : Swift.Int
0x100000e49 <+89>: seto %r8b
如上,全局变量并不会被闭包「捕获」,这是因为全局变量的作用域使得其无需捕获也可访问。