You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的执行逻辑

  1. 初始化与备份:函数创建引用类型实例后,将实例的引用地址存入栈上的变量存储位置(rbp-16),同时把该地址备份到栈的另一位置(rbp-24),这个备份就是最终要返回的引用。
  2. 执行defer:调用defer函数时,将变量的存储地址(rbp-16)通过rdi寄存器传递给defer。defer内部会创建新的实例,修改传入的变量存储位置里的引用地址,使其指向新实例,同时调用swift_release释放原来的旧实例(因为变量不再引用它)。
  3. 清理与返回:defer执行完毕后,主函数先释放变量当前引用的新实例(调用swift_release),最后将备份在rbp-24的旧实例地址加载到返回寄存器rax,作为函数的返回值。

核心逻辑和值类型一致:Swift会先确定返回值(值类型是副本,引用类型是引用地址的副本),再执行defer语句,所以defer对变量的修改不会影响已经确定的返回值。

内容的提问来源于stack exchange,提问作者Pyrettt Pyrettt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 05:50:27