15

Swift 拾遗 - inout

 3 years ago
source link: https://kingcos.me/posts/2020/swift_inout/
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-07-25 首次提交

Preface

《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。今天我们将一起简单探究修饰 Swift 中函数参数的 inout 关键字。

我们声明一个使用 inout 参数的函数,并在外界将变量 & 按地址传入:

var foo = 5

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

inoutDemoFunc(&foo) // BREAKPOINT 🔴

// foo == 9

尝试将以上代码转换为汇编(Xcode Menu - Debug - Debug Workflow - Always Show Disassembly):

; inout
demo`main:
    0x100001d60 <+0>:  pushq  %rbp
    0x100001d61 <+1>:  movq   %rsp, %rbp
    0x100001d64 <+4>:  subq   $0x30, %rsp
    0x100001d68 <+8>:  leaq   0x1459(%rip), %rax        ; leetcode.foo : Swift.Int
    0x100001d6f <+15>: xorl   %ecx, %ecx
    0x100001d71 <+17>: movq   $0x5, 0x144c(%rip)        ; _dyld_private + 4
    0x100001d7c <+28>: movl   %edi, -0x1c(%rbp)
->  0x100001d7f <+31>: movq   %rax, %rdi
    0x100001d82 <+34>: leaq   -0x18(%rbp), %rax
    0x100001d86 <+38>: movq   %rsi, -0x28(%rbp)
    0x100001d8a <+42>: movq   %rax, %rsi
    0x100001d8d <+45>: movl   $0x21, %edx
    0x100001d92 <+50>: callq  0x100001e22               ; symbol stub for: swift_beginAccess
    ; 参数传递 ⬇
    0x100001d97 <+55>: leaq   0x142a(%rip), %rdi        ; leetcode.foo : Swift.Int
    ; 函数调用 ⬇
    0x100001d9e <+62>: callq  0x100001dc0               ; leetcode.inoutDemoFunc(inout Swift.Int) -> () at main.swift:316
    0x100001da3 <+67>: leaq   -0x18(%rbp), %rdi
    0x100001da7 <+71>: callq  0x100001e28               ; symbol stub for: swift_endAccess
    0x100001dac <+76>: xorl   %eax, %eax
    0x100001dae <+78>: addq   $0x30, %rsp
    0x100001db2 <+82>: popq   %rbp
    0x100001db3 <+83>: retq

; Step Into
demo`inoutDemoFunc(_:):
    0x100001dc0 <+0>:  pushq  %rbp
    0x100001dc1 <+1>:  movq   %rsp, %rbp
    0x100001dc4 <+4>:  movq   $0x0, -0x8(%rbp)
    0x100001dcc <+12>: movq   %rdi, -0x8(%rbp)
    ; $0x9 即立即数 9 ⬇
->  0x100001dd0 <+16>: movq   $0x9, (%rdi)
    0x100001dd7 <+23>: popq   %rbp
    0x100001dd8 <+24>: retq

观察整个函数调用流程我们会发现,当使用 inout 时,函数参数的内存地址将通过 lea 指令被放在 %rdi 中,传递到函数内部;在函数内部修改参数值时,实际修改的是 %rdi 中内存地址所对应的存储空间的实际值。这就验证了 inout 的本质是按引用传递。

我们再来尝试一个无 inout 修饰参数的函数:

var foo = 5

func demoFunc(_ innerFoo: Int) {
    // innerFoo 默认声明为 let
    var innerFoo = innerFoo
    innerFoo = 9
}

demoFunc(foo)  // BREAKPOINT 🔴

// foo == 5

同样将以上代码转换为汇编:

demo`main:
    0x100001d50 <+0>:  pushq  %rbp
    0x100001d51 <+1>:  movq   %rsp, %rbp
    0x100001d54 <+4>:  subq   $0x30, %rsp
    0x100001d58 <+8>:  leaq   0x1469(%rip), %rax        ; leetcode.foo : Swift.Int
    0x100001d5f <+15>: xorl   %ecx, %ecx
    0x100001d61 <+17>: movq   $0x5, 0x145c(%rip)        ; _dyld_private + 4
    0x100001d6c <+28>: movl   %edi, -0x1c(%rbp)
->  0x100001d6f <+31>: movq   %rax, %rdi
    0x100001d72 <+34>: leaq   -0x18(%rbp), %rax
    0x100001d76 <+38>: movq   %rsi, -0x28(%rbp)
    0x100001d7a <+42>: movq   %rax, %rsi
    0x100001d7d <+45>: movl   $0x20, %edx
    0x100001d82 <+50>: callq  0x100001e1e               ; symbol stub for: swift_beginAccess
    0x100001d87 <+55>: movq   0x143a(%rip), %rdi        ; leetcode.foo : Swift.Int
    0x100001d8e <+62>: leaq   -0x18(%rbp), %rax
    0x100001d92 <+66>: movq   %rdi, -0x30(%rbp)
    0x100001d96 <+70>: movq   %rax, %rdi
    0x100001d99 <+73>: callq  0x100001e24               ; symbol stub for: swift_endAccess
    ; 参数传递,但这里不同于 inout 使用 lea 指令所做的按地址传递 ⬇
    0x100001d9e <+78>: movq   -0x30(%rbp), %rdi
    ; 函数调用 ⬇
    0x100001da2 <+82>: callq  0x100001db0               ; leetcode.demoFunc(Swift.Int) -> () at main.swift:316
    0x100001da7 <+87>: xorl   %eax, %eax
    0x100001da9 <+89>: addq   $0x30, %rsp
    0x100001dad <+93>: popq   %rbp
    0x100001dae <+94>: retq

; Step Into
demo`demoFunc(_:):
    0x100001db0 <+0>:  pushq  %rbp
    0x100001db1 <+1>:  movq   %rsp, %rbp
    0x100001db4 <+4>:  movq   $0x0, -0x8(%rbp)
    0x100001dbc <+12>: movq   $0x0, -0x10(%rbp)
    0x100001dc4 <+20>: movq   %rdi, -0x8(%rbp)
->  0x100001dc8 <+24>: movq   %rdi, -0x10(%rbp)
    0x100001dcc <+28>: movq   $0x9, -0x10(%rbp)
    0x100001dd4 <+36>: popq   %rbp
    0x100001dd5 <+37>: retq

当没有使用 inout 时,函数参数值本身将通过 mov 指令被放在 %rdi 中;在函数内部再通过 mov 指令将 %rdi 中的值取出放在 -0x8(%rbp) 中,声明同名变量则是同样将 %rdi 中的值取出放在 -0x10(%rbp) 中,修改则也是针对 -0x10(%rbp) 中的值。这些过程均属于值传递,因此外界的变量也不会被改变。

- 用例 描述 % %rip 寄存器前缀为 %(ASCII 0x25) $ $0x9 立即数前缀为 $(ASCII 0x24) (%) (%rdi)%rdi 中存储的内存地址所对应的值(%rdi 为指针地址,(%rdi) 为地址对应的值) call
Call
调用 callq 0x100000b70 调用函数地址为 0x100000b70 的函数 lea
Load Effective Address
加载有效地址 leaq 0x142a(%rip), %rdi%rip 中的地址值加上 0x142a 并存储到 %rdi 中(地址传递) mov
Move
移动 movq $0x9, (%rdi)9 存储在 %rdi 中地址对应的存储空间中(*p = 9

汇编指令后缀

在上文中的汇编指令上,我们经常能见到 lq 等这样的汇编指令后缀,具体如下表:

标识符 全拼 位 b Byte 8 w Word 16 l Long 32(默认) q Quadword 64

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK