Swift defer语句内部原理:为何返回值未被defer修改?
Swift中defer在值类型与引用类型的执行差异分析
值类型示例
先看一个简单的值类型示例:
func square() -> Int { var x = 5 defer { x = 10 } return x }
为何返回的是5?
已知defer仅在rbp销毁前执行,也就是延迟到return阶段执行。对应的汇编代码如下:
output.square() -> Swift.Int: push rbp mov rbp, rsp sub rsp, 16 mov qword ptr [rbp - 8], 0 mov qword ptr [rbp - 8], 5 lea rdi, [rbp - 8] call ($defer #1 () -> () in output.square() -> Swift.Int) mov eax, 5 add rsp, 16 pop rbp ret $defer #1 () -> () in output.square() -> Swift.Int: push rbp mov rbp, rsp mov qword ptr [rbp - 8], 0 mov qword ptr [rbp - 8], rdi mov qword ptr [rdi], 10 pop rbp ret
关于寄存器的疑问解答
不是单纯因为寄存器不同导致的结果。核心原因是:Swift处理值类型返回时,会先将返回值的副本加载到返回寄存器(eax),之后才执行defer代码。虽然defer里修改了栈上的x(rbp-8位置),但此时返回值已经被提前存入eax,所以最终返回的是最初的5。寄存器只是承载返回值的载体,本质是值类型的返回机制——返回的是副本,而非栈上变量本身。
引用类型的执行逻辑
再看引用类型对应的汇编代码:
output.square() -> output.X: push rbp mov rbp, rsp push r13 sub rsp, 24 mov qword ptr [rbp - 16], 0 xor eax, eax mov edi, eax call (type metadata accessor for output.X) mov r13, rax call (output.X.__allocating_init() -> output.X) mov rdi, rax mov qword ptr [rbp - 24], rdi call swift_retain@PLT mov rax, qword ptr [rbp - 24] mov qword ptr [rbp - 16], rax lea rdi, [rbp - 16] call ($defer #1 () -> () in output.square() -> output.X) mov rdi, qword ptr [rbp - 16] call swift_release@PLT mov rax, qword ptr [rbp - 24] add rsp, 24 pop r13 pop rbp ret type metadata accessor for output.X: lea rax, [rip + (full type metadata for output.X)+16] xor ecx, ecx mov edx, ecx ret $defer #1 () -> () in output.square() -> output.X: push rbp mov rbp, rsp push r13 sub rsp, 24 mov qword ptr [rbp - 24], rdi mov qword ptr [rbp - 16], 0 mov qword ptr [rbp - 16], rdi xor eax, eax mov edi, eax call (type metadata accessor for output.X) mov r13, rax call (output.X.__allocating_init() -> output.X) mov rcx, rax mov rax, qword ptr [rbp - 24] mov rdi, qword ptr [rax] mov qword ptr [rax], rcx call swift_release@PLT add rsp, 24 pop r13 pop rbp ret
引用类型下defer的执行逻辑
- 初始化与备份:函数创建引用类型实例后,将实例的引用地址存入栈上的变量存储位置(
rbp-16),同时把该地址备份到栈的另一位置(rbp-24),这个备份就是最终要返回的引用。 - 执行defer:调用defer函数时,将变量的存储地址(
rbp-16)通过rdi寄存器传递给defer。defer内部会创建新的实例,修改传入的变量存储位置里的引用地址,使其指向新实例,同时调用swift_release释放原来的旧实例(因为变量不再引用它)。 - 清理与返回:defer执行完毕后,主函数先释放变量当前引用的新实例(调用
swift_release),最后将备份在rbp-24的旧实例地址加载到返回寄存器rax,作为函数的返回值。
核心逻辑和值类型一致:Swift会先确定返回值(值类型是副本,引用类型是引用地址的副本),再执行defer语句,所以defer对变量的修改不会影响已经确定的返回值。
内容的提问来源于stack exchange,提问作者Pyrettt Pyrettt
相关产品推荐
相关产品推荐

