Swift 中的 inout 本质是按引用传递

var foo = 5

func inoutDemoFunc(_ innerFoo: inout Int) {
    innerFoo = 9
}

inoutDemoFunc(&foo) // BREAKPOINT ⚠️

将以上代码转换为汇编:

; inout
demo`main:
    0x100000b10 <+0>:  pushq  %rbp
    0x100000b11 <+1>:  movq   %rsp, %rbp
    0x100000b14 <+4>:  subq   $0x30, %rsp
    0x100000b18 <+8>:  leaq   0x651(%rip), %rax         ; demo.foo : Swift.Int
    0x100000b1f <+15>: xorl   %ecx, %ecx
    0x100000b21 <+17>: movq   $0x5, 0x644(%rip)         ; _swift_FORCE_LOAD_$_swiftCompatibility50
    0x100000b2c <+28>: movl   %edi, -0x1c(%rbp)
->  0x100000b2f <+31>: movq   %rax, %rdi
    0x100000b32 <+34>: leaq   -0x18(%rbp), %rax
    0x100000b36 <+38>: movq   %rsi, -0x28(%rbp)
    0x100000b3a <+42>: movq   %rax, %rsi
    0x100000b3d <+45>: movl   $0x21, %edx
    0x100000b42 <+50>: callq  0x100000ec4               ; symbol stub for: swift_beginAccess
    ; 参数传递 ⬇
    0x100000b47 <+55>: leaq   0x622(%rip), %rdi         ; demo.foo : Swift.Int
    ; 函数调用 ⬇
    0x100000b4e <+62>: callq  0x100000b70               ; demo.inoutDemoFunc(inout Swift.Int) -> () at main.swift:11
    0x100000b53 <+67>: leaq   -0x18(%rbp), %rdi
    0x100000b57 <+71>: callq  0x100000eca               ; symbol stub for: swift_endAccess
    0x100000b5c <+76>: xorl   %eax, %eax
    0x100000b5e <+78>: addq   $0x30, %rsp
    0x100000b62 <+82>: popq   %rbp
    0x100000b63 <+83>: retq

; Step into
demo`inoutDemoFunc(_:):
    0x100000b70 <+0>:  pushq  %rbp
    0x100000b71 <+1>:  movq   %rsp, %rbp
    0x100000b74 <+4>:  movq   $0x0, -0x8(%rbp)
    0x100000b7c <+12>: movq   %rdi, -0x8(%rbp)
    ; $0x9 即立即数 9 ⬇
->  0x100000b80 <+16>: movq   $0x9, (%rdi)
    0x100000b87 <+23>: popq   %rbp
    0x100000b88 <+24>: retq
  • Swift 中的函数调用在汇编中表示为 callq 指令;如上指令中的 callq 0x100000b70 即调用函数地址为 0x100000b70 的函数,但这一行并不包括传参,其参数传递由 leaq 0x622(%rip), %rdi 负责
  • lea 是用于地址传递的汇编指令;如上指令中的 leaq 0x622(%rip), %rdi 即将 rip 寄存器中的地址值与 0x622 加和并存储到 rdi 寄存器中(汇编语言中类似 %rip%rdi 等百分号开头的操作数均为寄存器)
  • movq $0x9, (%rdi) 即将 9 存储在 rdi 寄存器中地址对应的存储空间中((%rdi) 的小括号为寻找 rdi 寄存器中存储地址值所对应的存储空间)
  • 因此 inout 关键字修饰的参数即按地址将变量传入,在函数体内部赋值即直接修改地址对应的存储空间中的值
var foo = 5

func demoFunc(_ innerFoo: Int) {

}

demoFunc(foo)  // BREAKPOINT ⚠️
demo`main:
    0x100000b10 <+0>:  pushq  %rbp
    0x100000b11 <+1>:  movq   %rsp, %rbp
    0x100000b14 <+4>:  subq   $0x30, %rsp
    0x100000b18 <+8>:  leaq   0x651(%rip), %rax         ; demo.foo : Swift.Int
    0x100000b1f <+15>: xorl   %ecx, %ecx
    0x100000b21 <+17>: movq   $0x5, 0x644(%rip)         ; _swift_FORCE_LOAD_$_swiftCompatibility50
    0x100000b2c <+28>: movl   %edi, -0x1c(%rbp)
->  0x100000b2f <+31>: movq   %rax, %rdi
    0x100000b32 <+34>: leaq   -0x18(%rbp), %rax
    0x100000b36 <+38>: movq   %rsi, -0x28(%rbp)
    0x100000b3a <+42>: movq   %rax, %rsi
    0x100000b3d <+45>: movl   $0x20, %edx
    0x100000b42 <+50>: callq  0x100000ec4               ; symbol stub for: swift_beginAccess
    0x100000b47 <+55>: movq   0x622(%rip), %rdi         ; demo.foo : Swift.Int
    0x100000b4e <+62>: leaq   -0x18(%rbp), %rax
    0x100000b52 <+66>: movq   %rdi, -0x30(%rbp)
    0x100000b56 <+70>: movq   %rax, %rdi
    0x100000b59 <+73>: callq  0x100000eca               ; symbol stub for: swift_endAccess
    ; 参数传递,但这里不同于 inout 使用 lea 指令所做的按地址传递 ⬇
    0x100000b5e <+78>: movq   -0x30(%rbp), %rdi
    ; 函数调用 ⬇
    0x100000b62 <+82>: callq  0x100000b70               ; demo.demoFunc(Swift.Int) -> () at main.swift:17
    0x100000b67 <+87>: xorl   %eax, %eax
    0x100000b69 <+89>: addq   $0x30, %rsp
    0x100000b6d <+93>: popq   %rbp
    0x100000b6e <+94>: retq

; Step into
demo`demoFunc(_:):
    0x100000b70 <+0>:  pushq  %rbp
    0x100000b71 <+1>:  movq   %rsp, %rbp
    0x100000b74 <+4>:  movq   $0x0, -0x8(%rbp)
    0x100000b7c <+12>: movq   %rdi, -0x8(%rbp)
->  0x100000b80 <+16>: popq   %rbp
    0x100000b81 <+17>: retq
  • movq -0x30(%rbp), %rdirdp 减去 0x30foo 占用的内存空间中存储的 10 赋值到 rdi
  • 可被多次赋值的变量即可作为 inout 参数传入