Recently, we saw a swift topic in a development group, and we discussed a wave of it. For a while, we were curious about the reason, so we studied a wave and recorded it again
var a = 1
func add() -> Int {
defer {
a = a + 1
}
return a
}
print(a) // Here print out a is 1?Copy the code
Print (a) says 1, but why does it say 1? I’m going to keep you in suspense. Let’s move on.
defer
Some understanding of
aboutdefer
Statements, inswift
What is it used in?
For example, to read the contents of a file in a directory and process the data, you need to locate the file directory, open the folder, read the contents of the file and process the data, and close the file and folder. If all goes well, just follow the set procedure one round;
However, things don’t always go your way, and if something fails, such as reading the contents of the file, processing the data, etc., there are some finishing touches that need to be done to close the file or close the folder (even if it works, of course). Since an exception may occur before the file is closed and the code can’t proceed, which will result in a memory leak, defer can handle this by putting the wrap up in the defer block to make sure the wrap up goes smoothly.
The above is my overall impression of defer. I have also checked the information about the explanation of defer:
Please refer to the description of defer in swift official documentation
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 also has instructions for this
The statements in the DEFER statement will be executed regardless of how program control is transferred. You can use the defer statement in some cases, for example, when you manage resources manually, such as closing file descriptors, or when you need to do something even if an error is thrown. If multiple defer statements appear in the same scope, they will be executed in the opposite order as they appear. The first defer statement in a given scope is executed last, which means that resources referenced in the last defer statement in the code can be cleaned up by other defer statements.
In the titleprint(a)
The cause of snooping
Why print(a)t prints 1 instead of 2?
Recently I happened to learn assembly, and then wrote a demo, to follow the assembly to see what is going on?
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 forSwift.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 stubfor: 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
Copy the code
The above code is mainly divided into three parts: main, add() and defer. There is no need to talk about main. Add () is the assembly of func add(){} defined in the title, while defer is the assembly implementation corresponding to the defer in func add(){}. The main function call steps are described in the following steps
- in
main
In, we see that1
Is assigned to the global variable area0x1000011A0
; 0x1000008DB <+11>: movQ in main$0x1, 0x8ba(%rip) ; _swift_FORCE_LOAD_$_swiftCompatibility50// Store 1 in the global variable area at 0x1000011A0 0x1000008ed <+29>: callq 0x100000a00; Swiftlearning.add () -> swift.int at main. Swift :13Copy the code
- I’m going to talk about
callq 0x100000a00
Came toadd()
Function, the key part of which is interpreted as follows
; Add 0x100000a82 <+34>: movq 0x717(%rip), %rax; swiftLearning.a : Swift.Int// remove the global variable and assign it to rax, you can see that 0x717(%rip) is the address 0x717+0x100000a89=0x1000011A0, that is, 1 0x100000a89 <+41>: leaq -0x18(%rbp), %rdi 0x100000a8d <+45>: movq %rax, -0x38(%rbp) ; -0x38(% RBP) 0x100000a33 <+51>: movq 0x766(%rip), %rax; swiftLearning.a : Swift.Int = 0x766+0x100000a3a=0x1000011A0 Swift.Int = 0x766+0x100000a3a <+58> movq -0x28(%rbp), %rdi 0x100000a3e <+62>: movq %rax, -0x30(%rbp) ; This step places 1 in cache -0x30(% RBP) 0x100000a42 <+66>: callq 0x100000e92; symbol stubfor: swift_endAccess
-> 0x100000a47 <+71>: callq 0x100000a60 ; $defer #1 () -> () in Calls the defer function here
Copy the code
- By the time we got to defer, we had already called it
Defer function
0x100000a91 <+49>: callq 0x100000e92 ; symbol stub for: swift_endAccess 0x100000a96 <+54>: movq -0x38(%rbp), %rax ; Remove the address from memory area -0x38(% RBP) and pass it to RAX. At this time, the address in RAX is 0x1000011A0 0x100000a9A <+58>: INCq %rax; The value stored in RAX increasesCopy the code
- The function defer knows ret in the code from now on and has not operated on rax again. By this point in the code, we have seen that a = A + 1 in defer, but is that all? Let’s move on to the key code
0x100000a33 <+51>: movq 0x766(%rip), %rax ; Swiftlearning. a: swiftlearning. Int Assigns the address of global area 0x1000011A0 to raX register 0x100000a3a <+58>: MOVq-0x28 (% RBP), %rdi 0x100000a3e <+62>: movq %rax, -0x30(%rbp) ; Here the raX register value is paid to the memory area -0x30(% RBP) save 0x100000a42 <+66>: callq 0x100000e92; symbol stubfor: swift_endAccess
-> 0x100000a47 <+71>: callq 0x100000a60 ; $defer #1 () -> () in swiftlearning.add () -> swift.int at 0x100000a4c <+76>: movq -0x30(%rbp), %rax ; Here rax is assigned again, and the -0x30(% RBP) cache here is the 0x1000011A0 stored before the defer call, from which RAx is assigned address 0x1000011A0 0x100000a50 <+80>: addQ$0x30, %rsp 0x100000a54 <+84>: popq %rbp 0x100000a55 <+85>: retq ; I'm just done with the add() method andreturnGo back toCopy the code
- when
add
Directly after the executionrax
As the address of the return value, return andprint
And then you get that 1, which is kind of a full circle, actuallyrax
The value of theta is still taken0x1000011A0
“And did not take itdefer
Is the value after the increment operation
Here we can see that although a = a +1 was implemented in defer, before the add function return, rax was assigned 0x1000011A0, and before the function call we already saw 0x1000008DB <+11>: Movq $0x1, 0x8ba(%rip), we can get that after the add execution, the return value is still 0x1000011A0, which explains why the print is still 1
(lldb) register read rax
rax = 0x0000000000000001
1
Copy the code
To summarize, although defer was executed before the function returned, it did not affect the return value, which was retrieved after defer, so the value of print was still 1