一道關於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

相關文章
相關標籤/搜索