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

近期在某开发群里面看到一道swift的题目,大家讨论了一波,一时好奇其原因,就自己研究了一波,再次记录一下

var a = 1

func add() -> Int {

    defer {

        a = a + 1

    }

    return a

}

print(a) // 这里打印出来a是1?

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

defer的一些理解

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

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

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

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

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

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 语句清理掉。

题目中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   

 

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

在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()方法中

接下来会在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函数

   

到这里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寄存其中的值自增

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回去

当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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容