一道关于swift中defer的面试题目的窥探

近期在某开发群里面看到一道swift的题目,你们讨论了一波,一时好奇其缘由,就本身研究了一波,再次记录一下html

var a = 1
func add() -> Int {
    defer {
        a = a + 1
    }
    return a
}
print(a) // 这里打印出来a是1?
复制代码

这里我已经把答案写出来了,print(a)打印的是1,可为啥会是1呢?这里我先卖个关子,咱们继续往下看。git

defer的一些理解

关于defer语句,在swift中它被应用于什么场景呢?swift

好比,读取某目录下的文件内容并处理数据,你须要首先定位到文件目录,打开文件夹,读取文件内容以及处理数据,关闭文件以及文件夹。假若一切顺利,只需按照设定好的程序流程走一轮便可;缓存

然而,事情并不会老是如你所愿,若是中间某个环节失败,好比读取文件内容失败、处理数据失败等等,还须要进行一些后续收尾工做,即关闭文件或关闭文件夹(固然就算顺利执行,也是要关闭的)。 因为在关闭文件以前可能出现异常,致使代码没法继续往下执行,这将会致使内存泄漏,那么这时候defer就能够来处理这种问题,将收尾工做写在defer代码块中,保证收尾工做顺利进行。bash

以上即是我对于defer的总体印象,我也查了下资料关于defer的说明:less

关于swift官方文档中对于defer的说明ide

Use defer to write a block of code that is executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error. You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.函数

SwiftGG对其也有说明学习

在 defer 语句中的语句不管程序控制如何转移都会被执行。在某些状况下,例如,手动管理资源时,好比关闭文件描述符,或者即便抛出了错误也须要执行一些操做时,就可使用 defer 语句。 若是多个 defer 语句出如今同一做用域内,那么它们执行的顺序与出现的顺序相反。给定做用域中的第一个 defer 语句,会在最后执行,这意味着代码中最靠后的 defer 语句中引用的资源能够被其余 defer 语句清理掉。ui

题目中print(a)的缘由窥探

以上的这些说明不足以解释这个咱们的主题,为啥print(a)t打印出来的是1,而不是2?

最近恰好在学习汇编,随即写了个demo,来跟一下汇编看看究竟是咋回事?

swiftLearning`main:
    0x1000008d0 <+0>:   pushq  %rbp
    0x1000008d1 <+1>:   movq   %rsp, %rbp
    0x1000008d4 <+4>:   subq   $0x80, %rsp
    0x1000008db <+11>:  movq   $0x1, 0x8ba(%rip)         ; _swift_FORCE_LOAD_$_swiftCompatibility50
    0x1000008e6 <+22>:  movl   %edi, -0x34(%rbp)
    0x1000008e9 <+25>:  movq   %rsi, -0x40(%rbp)
->  0x1000008ed <+29>:  callq  0x100000a00               ; swiftLearning.add() -> Swift.Int at main.swift:13
    0x1000008f2 <+34>:  leaq   0x8a7(%rip), %rsi         ; swiftLearning.a : Swift.Int
    0x1000008f9 <+41>:  xorl   %edi, %edi
    0x1000008fb <+43>:  movl   %edi, %ecx
    0x1000008fd <+45>:  movq   %rsi, %rdi
    0x100000900 <+48>:  leaq   -0x18(%rbp), %rsi
    0x100000904 <+52>:  movl   $0x21, %edx
    0x100000909 <+57>:  movq   %rax, -0x48(%rbp)
    0x10000090d <+61>:  callq  0x100000e86               ; symbol stub for: swift_beginAccess
    0x100000912 <+66>:  movq   -0x48(%rbp), %rax
    0x100000916 <+70>:  movq   %rax, 0x883(%rip)         ; swiftLearning.a : Swift.Int
    0x10000091d <+77>:  leaq   -0x18(%rbp), %rdi
    0x100000921 <+81>:  callq  0x100000e92               ; symbol stub for: swift_endAccess
    0x100000926 <+86>:  movq   0x6e3(%rip), %rax         ; (void *)0x00007fff9cf6eb18: type metadata for Any
    0x10000092d <+93>:  addq   $0x8, %rax
    0x100000931 <+97>:  movl   $0x1, %edi
    0x100000936 <+102>: movq   %rax, %rsi
    0x100000939 <+105>: callq  0x100000e50               ; symbol stub for: Swift._allocateUninitializedArray<A>(Builtin.Word) -> (Swift.Array<A>, Builtin.RawPointer)
    0x10000093e <+110>: leaq   0x85b(%rip), %rcx         ; swiftLearning.a : Swift.Int
    0x100000945 <+117>: xorl   %r8d, %r8d
    0x100000948 <+120>: movl   %r8d, %esi
    0x10000094b <+123>: movq   %rcx, %rdi
    0x10000094e <+126>: leaq   -0x30(%rbp), %rcx
    0x100000952 <+130>: movq   %rsi, -0x50(%rbp)
    0x100000956 <+134>: movq   %rcx, %rsi
    0x100000959 <+137>: movl   $0x20, %ecx
    0x10000095e <+142>: movq   %rdx, -0x58(%rbp)
    0x100000962 <+146>: movq   %rcx, %rdx
    0x100000965 <+149>: movq   -0x50(%rbp), %rcx
    0x100000969 <+153>: movq   %rax, -0x60(%rbp)
    0x10000096d <+157>: callq  0x100000e86               ; symbol stub for: swift_beginAccess
    0x100000972 <+162>: movq   0x827(%rip), %rax         ; swiftLearning.a : Swift.Int
    0x100000979 <+169>: leaq   -0x30(%rbp), %rdi
    0x10000097d <+173>: movq   %rax, -0x68(%rbp)
    0x100000981 <+177>: callq  0x100000e92               ; symbol stub for: swift_endAccess
    0x100000986 <+182>: movq   0x67b(%rip), %rax         ; (void *)0x00007fff9cf64ff8: type metadata for Swift.Int
    0x10000098d <+189>: movq   -0x58(%rbp), %rcx
    0x100000991 <+193>: movq   %rax, 0x18(%rcx)
    0x100000995 <+197>: movq   -0x68(%rbp), %rax
    0x100000999 <+201>: movq   %rax, (%rcx)
    0x10000099c <+204>: callq  0x100000b00               ; default argument 1 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () at <compiler-generated>
    0x1000009a1 <+209>: movq   %rax, -0x70(%rbp)
    0x1000009a5 <+213>: movq   %rdx, -0x78(%rbp)
    0x1000009a9 <+217>: callq  0x100000b20               ; default argument 2 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () at <compiler-generated>
    0x1000009ae <+222>: movq   -0x60(%rbp), %rdi
    0x1000009b2 <+226>: movq   -0x70(%rbp), %rsi
    0x1000009b6 <+230>: movq   -0x78(%rbp), %rcx
    0x1000009ba <+234>: movq   %rdx, -0x80(%rbp)
    0x1000009be <+238>: movq   %rcx, %rdx
    0x1000009c1 <+241>: movq   %rax, %rcx
    0x1000009c4 <+244>: movq   -0x80(%rbp), %r8
    0x1000009c8 <+248>: callq  0x100000e56               ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
    0x1000009cd <+253>: movq   -0x80(%rbp), %rdi
    0x1000009d1 <+257>: callq  0x100000e8c               ; symbol stub for: swift_bridgeObjectRelease
    0x1000009d6 <+262>: movq   -0x78(%rbp), %rdi
    0x1000009da <+266>: callq  0x100000e8c               ; symbol stub for: swift_bridgeObjectRelease
    0x1000009df <+271>: movq   -0x60(%rbp), %rdi
    0x1000009e3 <+275>: callq  0x100000e8c               ; symbol stub for: swift_bridgeObjectRelease
    0x1000009e8 <+280>: xorl   %eax, %eax
    0x1000009ea <+282>: addq   $0x80, %rsp
    0x1000009f1 <+289>: popq   %rbp
    0x1000009f2 <+290>: retq   
 

swiftLearning`add():
    0x100000a00 <+0>:  pushq  %rbp
    0x100000a01 <+1>:  movq   %rsp, %rbp
    0x100000a04 <+4>:  subq   $0x30, %rsp
    0x100000a08 <+8>:  leaq   0x791(%rip), %rdi         ; swiftLearning.a : Swift.Int
    0x100000a0f <+15>: xorl   %eax, %eax
    0x100000a11 <+17>: movl   %eax, %ecx
    0x100000a13 <+19>: leaq   -0x18(%rbp), %rdx
    0x100000a17 <+23>: movl   $0x20, %esi
    0x100000a1c <+28>: movq   %rsi, -0x20(%rbp)
    0x100000a20 <+32>: movq   %rdx, %rsi
    0x100000a23 <+35>: movq   -0x20(%rbp), %r8
    0x100000a27 <+39>: movq   %rdx, -0x28(%rbp)
    0x100000a2b <+43>: movq   %r8, %rdx
    0x100000a2e <+46>: callq  0x100000e86               ; symbol stub for: swift_beginAccess
    0x100000a33 <+51>: movq   0x766(%rip), %rax         ; swiftLearning.a : Swift.Int
    0x100000a3a <+58>: movq   -0x28(%rbp), %rdi
    0x100000a3e <+62>: movq   %rax, -0x30(%rbp)
    0x100000a42 <+66>: callq  0x100000e92               ; symbol stub for: swift_endAccess
->  0x100000a47 <+71>: callq  0x100000a60               ; $defer #1 () -> () in swiftLearning.add() -> Swift.Int at <compiler-generated>
    0x100000a4c <+76>: movq   -0x30(%rbp), %rax
    0x100000a50 <+80>: addq   $0x30, %rsp
    0x100000a54 <+84>: popq   %rbp
    0x100000a55 <+85>: retq   
  
swiftLearning`$defer #1 () in add():
    0x100000a60 <+0>:   pushq  %rbp
    0x100000a61 <+1>:   movq   %rsp, %rbp
    0x100000a64 <+4>:   subq   $0x60, %rsp
    0x100000a68 <+8>:   leaq   0x731(%rip), %rax         ; swiftLearning.a : Swift.Int
    0x100000a6f <+15>:  xorl   %ecx, %ecx
    0x100000a71 <+17>:  movq   %rax, %rdi
    0x100000a74 <+20>:  leaq   -0x18(%rbp), %rsi
    0x100000a78 <+24>:  movl   $0x20, %edx
    0x100000a7d <+29>:  callq  0x100000e86               ; symbol stub for: swift_beginAccess
    0x100000a82 <+34>:  movq   0x717(%rip), %rax         ; swiftLearning.a : Swift.Int
    0x100000a89 <+41>:  leaq   -0x18(%rbp), %rdi
    0x100000a8d <+45>:  movq   %rax, -0x38(%rbp)
    0x100000a91 <+49>:  callq  0x100000e92               ; symbol stub for: swift_endAccess
->  0x100000a96 <+54>:  movq   -0x38(%rbp), %rax
    0x100000a9a <+58>:  incq   %rax
    0x100000a9d <+61>:  seto   %r8b
    0x100000aa1 <+65>:  movq   %rax, -0x40(%rbp)
    0x100000aa5 <+69>:  movb   %r8b, -0x41(%rbp)
    0x100000aa9 <+73>:  jo     0x100000af0               ; <+144> at main.swift:15:15
    0x100000aab <+75>:  leaq   0x6ee(%rip), %rdi         ; swiftLearning.a : Swift.Int
    0x100000ab2 <+82>:  xorl   %eax, %eax
    0x100000ab4 <+84>:  movl   %eax, %ecx
    0x100000ab6 <+86>:  leaq   -0x30(%rbp), %rdx
    0x100000aba <+90>:  movl   $0x21, %esi
    0x100000abf <+95>:  movq   %rsi, -0x50(%rbp)
    0x100000ac3 <+99>:  movq   %rdx, %rsi
    0x100000ac6 <+102>: movq   -0x50(%rbp), %r8
    0x100000aca <+106>: movq   %rdx, -0x58(%rbp)
    0x100000ace <+110>: movq   %r8, %rdx
    0x100000ad1 <+113>: callq  0x100000e86               ; symbol stub for: swift_beginAccess
    0x100000ad6 <+118>: movq   -0x40(%rbp), %rcx
    0x100000ada <+122>: movq   %rcx, 0x6bf(%rip)         ; swiftLearning.a : Swift.Int
    0x100000ae1 <+129>: movq   -0x58(%rbp), %rdi
    0x100000ae5 <+133>: callq  0x100000e92               ; symbol stub for: swift_endAccess
    0x100000aea <+138>: addq   $0x60, %rsp
    0x100000aee <+142>: popq   %rbp
    0x100000aef <+143>: retq   
    0x100000af0 <+144>: ud2    
  

复制代码

上面的代码中主要分为三部分,mainadd(),defer,其中main就不用多说了,add()就是咱们题目中定义的func add(){}的汇编,而defer则是func add(){}中的defer所对应的汇编实现了,主要的函数调用步骤我分为如下几步说明

  1. main中,咱们看到1被赋值到全局变量区0x1000011A0
;main函数中
    0x1000008db <+11>:  movq   $0x1, 0x8ba(%rip)         ; _swift_FORCE_LOAD_$_swiftCompatibility50 // 将1存放到全局变量区,地址为0x1000011A0
    0x1000008ed <+29>:  callq  0x100000a00               ; swiftLearning.add() -> Swift.Int at main.swift:13 这里就要调用到add()方法中
复制代码
  1. 接下来会在callq 0x100000a00来到add()函数中,其中的重点部分解读以下
;add函数中
    0x100000a82 <+34>:  movq   0x717(%rip), %rax         ; swiftLearning.a : Swift.Int// 将全局变量取出来赋值给rax,能够看到此处的0x717(%rip)就是地址0x717+0x100000a89=0x1000011A0,也就是刚才存起来的1
    0x100000a89 <+41>:  leaq   -0x18(%rbp), %rdi
    0x100000a8d <+45>:  movq   %rax, -0x38(%rbp)         ;将rax中的地址取出来,传给内存区域-0x38(%rbp) 
    
    0x100000a33 <+51>: movq   0x766(%rip), %rax         ; swiftLearning.a : Swift.Int 将全局变量取出来赋值给rax,能够看到此处的0x766(%rip)就是地址0x766+0x100000a3a=0x1000011A0,也就是刚才存起来的1
    0x100000a3a <+58>: movq   -0x28(%rbp), %rdi
    0x100000a3e <+62>: movq   %rax, -0x30(%rbp)         ;这一步将1放在缓存-0x30(%rbp) 中
    0x100000a42 <+66>: callq  0x100000e92               ; symbol stub for: swift_endAccess
->  0x100000a47 <+71>: callq  0x100000a60               ; $defer #1 () -> () in 这里调用defer函数
    
复制代码
  1. 到这里defer以前已经调用完毕,接下来是defer函数
0x100000a91 <+49>:  callq  0x100000e92               ; symbol stub for: swift_endAccess
    0x100000a96 <+54>:  movq   -0x38(%rbp), %rax         ; 将内存区域-0x38(%rbp) 中的地址去取来,再传给rax,此时rax中的地址是0x1000011A0
    0x100000a9a <+58>:  incq   %rax                      ; 将rax寄存其中的值自增
复制代码
  1. defer函数在此后代码中知道ret,再没有对rax进行操做,代码到这里,咱们已将看到defer中的a = a + 1,可是这就完了么?咱们继续往下解读重点代码
0x100000a33 <+51>: movq   0x766(%rip), %rax         ; swiftLearning.a : Swift.Int 这里将全局区0x1000011A0的地址赋值给了rax寄存器
    0x100000a3a <+58>: movq   -0x28(%rbp), %rdi
    0x100000a3e <+62>: movq   %rax, -0x30(%rbp)         ; 这里将rax寄存器的值付给了内存区域-0x30(%rbp)保存起来
    0x100000a42 <+66>: callq  0x100000e92               ; symbol stub for: swift_endAccess
->  0x100000a47 <+71>: callq  0x100000a60               ; $defer #1 () -> () in swiftLearning.add() -> Swift.Int at <compiler-generated>这里调用defer 这一步执行完之后,defer中的代码就调用完了
    0x100000a4c <+76>: movq   -0x30(%rbp), %rax         ; 这里又给rax赋值了,而这里的-0x30(%rbp)缓存区放的值就是在defer调用以前存入的0x1000011A0,自此rax又被赋值为地址0x1000011A0
    0x100000a50 <+80>: addq   $0x30, %rsp
    0x100000a54 <+84>: popq   %rbp
    0x100000a55 <+85>: retq                             ; 这里才把add()方法执行完,并return回去
复制代码
  1. add执行完之后直接将rax的地址当作返回值的地址,返回并print,此时就获得了那个1,至关于饶了一大圈,其实rax的值仍是取的0x1000011A0,并无拿defer中自增操做后的值

到这里咱们能够看到,虽然defer中执行了a = a + 1,可是在add函数return以前,rax又被赋值0x1000011A0,而在函数调用前,咱们已经看到0x1000008db <+11>: movq $0x1, 0x8ba(%rip),能够获得咱们在add执行完以后,返回值仍是0x1000011A0,解释了为啥print打印出来仍是1的缘由

(lldb) register read rax
     rax = 0x0000000000000001
1
复制代码

这里总结一下,虽然defer是在函数返回以前会执行,可是里面的操做并不会影响返回值,返回值在defer执行完以后,又去取了原来的值,因此print的值仍是1

相关文章
相关标签/搜索