当前位置:网站首页>Go deep into the implementation principle of go language defer
Go deep into the implementation principle of go language defer
2022-06-24 16:15:00 【luozhiyun】
Please state the source of reprint ~, This article was published at luozhiyun The blog of : https://www.luozhiyun.com/archives/523
This article uses go Source code 1.15.7
Introduce
defer Enforcement rules
Multiple defer The execution order of is " Last in, first out LIFO "
package main
import (
"fmt"
)
func main() {
name := "Naveen"
fmt.Printf("Original String: %s\n", string(name))
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
} In the example above , Use for Loop the string Naveen After traversing defer, these defer The call is like being pressed on a stack , The last one to be pushed into the stack defer The call will be pulled out and executed first .
The output is as follows :
$ go run main.go Original String: Naveen Reversed String: neevaN
defer When declared, the value of the determined parameter is calculated first
func a() {
i := 0
defer fmt.Println(i) // 0
i++
return
} In this case , Variable i stay defer It's determined when it's called , Not in defer When it comes to execution , So the output of the above statement is 0.
defer You can modify the return value of a function with a known return value
As the official said :
For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.
What's said above is , If one is defer The function called is a function literal, That is, closures or anonymous functions , And call defer A function with a named return value (named result parameters) Function of , that defer You can directly access the return value and modify it .
Examples are as follows :
// f returns 42
func f() (result int) {
defer func() {
result *= 7
}()
return 6
}But here's the thing , You can only modify the return value (named result parameters) function , Anonymous return value functions cannot be modified , as follows :
// f returns 100
func f() int {
i := 100
defer func() {
i++
}()
return i
} Because the anonymous return value function is in return Is declared when executed , So in defer Statement can only access the function with a named return value , You can't directly access anonymous return value functions .
defer The type of
Go stay 1.13 edition And 1.14 Version pair defer Two optimizations were carried out , bring defer The performance overhead of is greatly reduced in most scenarios .
Heap allocation
stay Go 1.13 All before defer It's all distributed on the heap , At compile time :
- stay
deferInsert the position of the statementruntime.deferproc, When executed ,deferThe call is saved as aruntime._deferStructure , Deposit in Goroutine Of_deferThe front of the list ; - Insert... Before the function returns
runtime.deferreturn, When executed , From Goroutine Of_deferTake the first one from the listruntime._deferAnd in turn .
On the stack
Go 1.13 Version added deferprocStack Realized on the stack distribution defer, Compared to heap allocation , Stack allocation after function return _defer They are released , It saves the performance cost of memory allocation , Just maintain... Properly _defer A list of the links . According to the official documents , Doing so raises about 30% About performance .
Except for the distribution of positions , There is no essential difference between stack allocation and heap allocation .
It is worth noting that ,1.13 Not all of them in the version defer Can be distributed on the stack . In the loop defer, Whether it's displayed for loop , still goto The implicit loop formed , Can only use heap allocation , Even if you loop once, you can only use heap allocation :
func A1() {
for i := 0; i < 1; i++ {
defer println(i)
}
}
$ GOOS=linux GOARCH=amd64 go tool compile -S main.go
...
0x004e 00078 (main.go:5) CALL runtime.deferproc(SB)
...
0x005a 00090 (main.go:5) CALL runtime.deferreturn(SB)
0x005f 00095 (main.go:5) MOVQ 32(SP), BP
0x0064 00100 (main.go:5) ADDQ $40, SP
0x0068 00104 (main.go:5) RETOpen code
Go 1.14 The development code has been added to the version (open coded), The mechanism will defer Call the direct insert function to return before , Omit the runtime deferproc or deferprocStack operation ., This optimization can make defer The call overhead is from 1.13 Version of ~35ns Reduce to ~6ns about .
But you need to meet certain conditions to trigger :
- Compiler optimization is not disabled , That is, there is no setting
-gcflags "-N"; - Within the function
deferNot more than 8 individual , AndreturnStatements anddeferThe product of the number of statements does not exceed 15; - Functional
deferKeywords cannot be executed in a loop ;
defer Structure
type _defer struct {
siz int32 // Memory size of parameters and results
started bool
heap bool // Whether it's heap allocation
openDefer bool // Is it optimized by open coding
sp uintptr // Stack pointer
pc uintptr // Caller's program counter
fn *funcval // Function passed in
_panic *_panic
link *_defer //defer Linked list
fd unsafe.Pointer
varp uintptr
framepc uintptr
} The above parameters that need to be noted are siz、heap、fn、link、openDefer These parameters will be discussed in the following analysis .
analysis
In the analysis of this paper , Let's start with heap distribution , By the way defer Why is the execution rule of the law as mentioned at the beginning , And then we 'll talk about defer On the stack allocation and development coding related content .
The analysis starts with a function call as an entry , The students who don't understand the function call can have a look :《 Understand from the stack Go Language function call https://www.luozhiyun.com/archives/518 》.
Heap allocation
Call the return value of a well-known function
Let's start with the example mentioned above , Start with function calls and look at the heap allocation . It's important to note that I'm 1.15 edition Running the following example above does not directly allocate to the heap , I need to recompile it myself Go Source code let defer Force distribution on the heap :
file location :src/cmd/compile/internal/gc/ssa.go
func (s *state) stmt(n *Node) {
...
case ODEFER:
if s.hasOpenDefers {
s.openDeferRecord(n.Left)
} else {
d := callDefer
// It needs to be annotated here
// if n.Esc == EscNever {
// d = callDeferStack
// }
s.call(n.Left, d)
}
...
}If you don't know how to compile , Take a look at this article :《 How to compile and debug Go runtime Source code https://www.luozhiyun.com/archives/506 》
func main() {
f()
}
func f() (result int) {
defer func() {
result *= 7
}()
return 6
}Use the command to print the assembly :
$ GOOS=linux GOARCH=amd64 go tool compile -S -N -l main.go
Let's look at it first main function , There's nothing to say , Very simple call f function :
"".main STEXT size=54 args=0x0 locals=0x10
0x0000 00000 (main.go:3) TEXT "".main(SB), ABIInternal, $16-0
...
0x0020 00032 (main.go:4) CALL "".f(SB)
...Let's take a look at it in sections f Function calls :
"".f STEXT size=126 args=0x8 locals=0x20
0x0000 00000 (main.go:7) TEXT "".f(SB), ABIInternal, $32-8
...
0x001d 00029 (main.go:7) MOVQ $0, "".result+40(SP) ;; Constant 0 write in 40(SP)
0x0026 00038 (main.go:8) MOVL $8, (SP) ;; Constant 8 Put it on the top of the stack
0x002d 00045 (main.go:8) LEAQ "".f.func1·f(SB), AX ;; Will function f.func1·f Address write AX
0x0034 00052 (main.go:8) MOVQ AX, 8(SP) ;; Will function f.func1·f Address write 8(SP)
0x0039 00057 (main.go:8) LEAQ "".result+40(SP), AX ;; take 40(SP) The address value is written to AX
0x003e 00062 (main.go:8) MOVQ AX, 16(SP) ;; take AX The saved address is written to 16(SP)
0x0043 00067 (main.go:8) PCDATA $1, $0
0x0043 00067 (main.go:8) CALL runtime.deferproc(SB) ;; call runtime.deferproc function because defer The heap allocation calls runtime.deferproc function , So what's shown in this compilation is runtime.deferproc A piece of assembly before a function call , If seen 《 Understand from the stack Go Language function call https://www.luozhiyun.com/archives/518 》, So the above paragraph is very simple to understand .
because runtime.deferproc The arguments to a function are just two arguments , as follows :
func deferproc(siz int32, fn *funcval)
During a function call , The parameter passing is Press the stack from right to left in the parameter list , So at the top of the stack are constants 8, stay 8(SP) The position pressed in is the second parameter f.func1·f Function address .
There may be a question here , In the press in constant 8 When the size is int32 Occupy 4 Byte size , Why doesn't the second parameter come from 4(SP) Start , But from 8(SP) Start , This is due to the need for memory alignment .
In addition to parameters , The other thing to note is that 16(SP) What's pressed into the position is 40(SP) Address value of . So the whole stack structure before calling should be as follows :
Let's take a look runtime.deferproc:
file location :src/runtime/panic.go
func deferproc(siz int32, fn *funcval) {
if getg().m.curg != getg() {
throw("defer on system stack")
}
// obtain sp The pointer
sp := getcallersp()
// obtain fn Function with a pointer as an argument
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc()
// Get a new one defer
d := newdefer(siz)
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
// take defer Add to the list
d.link = gp._defer
gp._defer = d
d.fn = fn
d.pc = callerpc
d.sp = sp
// Copy the parameters
switch siz {
case 0:
// If defered If the parameter of a function has only the size of a pointer, it copies the parameter directly through assignment
case sys.PtrSize:
// take argp The corresponding value Write to deferArgs In the returned address
*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
default:
// If the parameter size is not the pointer size , So make a data copy
memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
}
return0()
} Calling deferproc Function , We know , Parameters siz The value passed in is the value at the top of the stack, which means that the parameter size is 8 , Parameters fn Incoming 8(SP) The corresponding address .
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) ... *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
So the combination of the above two sentences is actually a combination of the above two sentences 16(SP) The address values saved in are saved in defer Next next next 8bytes Memory block as defer Parameters of . A simple sketch should be as follows ,defer Next to it argp What's actually in it is 16(SP) The address value saved in :
It should be noted that , This will be copied through the copy operation argp value , So in defer When called , The parameters have been determined , Instead of waiting for the execution to decide , But here's a copy of the address value .
And we know , When allocating on the heap ,defer It will be stored in the current Goroutine in , If there is 3 individual defer Called separately , Then the last call will be at the front of the linked list :
about newdefer This function is generally from P In the local cache pool of , If you can't get it, you can get it from sched Get half of it in the global cache pool of defer Fill in P Local resource pool for , If there is still no cache available , Allocate new... Directly from the heap defer and args . The idea of memory allocation and memory allocator allocation is similar , No more analysis , If you are interested, you can have a look at it yourself .
Now let's go back to f In the assembly of functions :
"".f STEXT size=126 args=0x8 locals=0x20
...
0x004e 00078 (main.go:11) MOVQ $6, "".result+40(SP) ;; Constant 6 write in 40(SP), As return value
0x0057 00087 (main.go:11) XCHGL AX, AX
0x0058 00088 (main.go:11) CALL runtime.deferreturn(SB) ;; call runtime.deferreturn function
0x005d 00093 (main.go:11) MOVQ 24(SP), BP
0x0062 00098 (main.go:11) ADDQ $32, SP
0x0066 00102 (main.go:11) RET It's very simple here , Just put the constant 6 Write to 40(SP) As the return value , And then call runtime.deferreturn perform defer.
Let's take a look runtime.deferreturn:
file location :src/runtime/panic.go
func deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
// determine defer Is the caller of the current deferreturn The caller of
sp := getcallersp()
if d.sp != sp {
return
}
switch d.siz {
case 0:
// Do nothing.
case sys.PtrSize:
// take defer Copy the saved parameters
// arg0 It's actually caller SP Top of stack address value , So here you're actually copying the parameters to caller SP Top of stack address value
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
default:
// If the parameter size is not sys.PtrSize, So make a data copy
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
fn := d.fn
d.fn = nil
gp._defer = d.link
// take defer The object is put into defer In the pool , It can be reused later
freedefer(d)
_ = fn.fn
// Pass in the functions and parameters to be executed
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
} The first thing to notice is , The parameter passed in here arg0 It's actually caller The value at the top of the stack for the caller , So the following assignment is actually to defer Copy the parameters of to caller The top of the caller stack :
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
*(*uintptr)(deferArgs(d)) What's kept here is actually caller The caller 16(SP) Saved address values . that caller The stack frame of the caller is shown in the following figure :
Let's go down to runtime.jmpdefer Take a look at how to achieve :
Location :src/runtime/asm_amd64.s
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16 MOVQ fv+0(FP), DX // fn Function address MOVQ argp+8(FP), BX // caller sp The caller SP LEAQ -8(BX), SP // caller The caller after SP MOVQ -8(SP), BP // caller The caller after BP SUBQ $5, (SP) // obtain runtime.deferreturn The address value is written to the top of the stack MOVQ 0(DX), BX // BX = DX JMP BX // Execution by defer Function of
This compilation is very interesting ,jmpdefer Function because it is runtime.deferreturn Called , So now the call stack frame is :
The incoming to jmpdefer The argument to the function is 0(FP) Express fn Function address , as well as 8(FP) It means f Function call stack SP.
So the following sentence stands for runtime.deferreturn Call stack's return address Write to SP:
LEAQ -8(BX), SP
that -8(SP) It stands for runtime.deferreturn Call stack's Base Pointer:
MOVQ -8(SP), BP
Now let's focus on explaining why SP The value the pointer points to minus 5 Can be obtained runtime.deferreturn Address value of :
SUBQ $5, (SP)
We go back to f In the assembly of function calls :
(dlv) disass
TEXT main.f(SB) /data/gotest/main.go
...
main.go:11 0x45def8 e8a3e2fcff call $runtime.deferreturn
main.go:11 0x45defd 488b6c2418 mov rbp, qword ptr [rsp+0x18]
... Due to the end of the call runtime.deferreturn Function, you need to continue to return to 0x45defd Continue at address value , So in calling runtime.deferreturn Function in the corresponding stack frame return address In fact, that is 0x45defd.
And in the jmpdefer Function ,(SP) The corresponding value is runtime.deferreturn Call stack's return address, So will 0x45defd subtract 5 Just in time to get 0x45def8 , And this value is runtime.deferreturn The address value of the function .
So at the end, jump to f.func1 Function execution time , The call stack is as follows :
The call stack (SP) In fact, the location of the store points to deferreturn Pointer to function , So in f.func1 After the function is called, it will return to deferreturn function , until _defer Until there is no data in the chain :
func deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
...
} Let's take a brief look at f.func1 Function call :
"".f.func1 STEXT nosplit size=25 args=0x8 locals=0x0
0x0000 00000 (main.go:8) TEXT "".f.func1(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:8) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
0x0000 00000 (main.go:8) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0000 00000 (main.go:9) MOVQ "".&result+8(SP), AX ;; Will point to 6 Write the address value of AX
0x0005 00005 (main.go:9) MOVQ (AX), AX ;; take 6 Write to AX
0x0008 00008 (main.go:9) LEAQ (AX)(AX*2), CX ;; CX = 6*2 +6 =18
0x000c 00012 (main.go:9) LEAQ (AX)(CX*2), AX ;; AX = 18*2 + 6 =42
0x0010 00016 (main.go:9) MOVQ "".&result+8(SP), CX ;; Will point to 6 Write the address value of CX
0x0015 00021 (main.go:9) MOVQ AX, (CX) ;; take CX The value pointed to by the address value is changed to 42
0x0018 00024 (main.go:10) RETThe call here is very simple , Get 8(SP) The address value points to the data and then does the operation , Then write the result to the stack , return .
So here we basically put defer The whole process of the function call is shown to you through heap allocation . From the above analysis, we have basically answered defer You can modify how the return value of a function with a known return value is achieved , The answer is in fact defer Passed during the call defer A parameter is a pointer to a return value , So in the end defer The return value will be modified during execution .
Anonymous function return value call
So how is the anonymous return value function passed ? For example, the following :
// f returns 100
func f() int {
i := 100
defer func() {
i++
}()
return i
}Now print the assembly :
"".f STEXT size=139 args=0x8 locals=0x28
0x0000 00000 (main.go:7) TEXT "".f(SB), ABIInternal, $40-8
...
0x001d 00029 (main.go:7) MOVQ $0, "".~r0+48(SP) ;; Initialization return value
0x0026 00038 (main.go:8) MOVQ $100, "".i+24(SP) ;; Initialize parameters i
0x002f 00047 (main.go:9) MOVL $8, (SP)
0x0036 00054 (main.go:9) LEAQ "".f.func1·f(SB), AX
0x003d 00061 (main.go:9) MOVQ AX, 8(SP) ;; take f.func1·f The address value is written to 8(SP)
0x0042 00066 (main.go:9) LEAQ "".i+24(SP), AX
0x0047 00071 (main.go:9) MOVQ AX, 16(SP) ;; take 24(SP) The address value is written to 16(SP)
0x004c 00076 (main.go:9) PCDATA $1, $0
0x004c 00076 (main.go:9) CALL runtime.deferproc(SB)
0x0051 00081 (main.go:9) TESTL AX, AX
0x0053 00083 (main.go:9) JNE 113
0x0055 00085 (main.go:9) JMP 87
0x0057 00087 (main.go:12) MOVQ "".i+24(SP), AX ;; take 24(SP) Value 100 Write to AX
0x005c 00092 (main.go:12) MOVQ AX, "".~r0+48(SP) ;; Will value 100 Write to 48(SP)
0x0061 00097 (main.go:12) XCHGL AX, AX
0x0062 00098 (main.go:12) CALL runtime.deferreturn(SB)
0x0067 00103 (main.go:12) MOVQ 32(SP), BP
0x006c 00108 (main.go:12) ADDQ $40, SP
0x0070 00112 (main.go:12) RET In the above output, we can see that in the call of anonymous return value function, the constant will be used first 100 Write to 24(SP) in , And then 24(SP) Write the address value of to 16(SP) , And then when you write the return value, it's through MOVQ Instructions will be 24(SP) The value of is written to 48(SP) in , That is to say, it's all value copying , There is no copy pointer , So there is no change to the return value .
Summary
Let's compare the two through a diagram after calling runtime.deferreturn Stack frame situation :
It's obvious that the famous return value function will be in 16(SP) The address where the return value is stored , And the anonymous return value function will be in 16(SP) It's a place to keep 24(SP) The address of .
Through the above series of analysis, I also answered a few questions :
- defer How to pass parameters ?
When we analyze it above, we will find that , In execution deferproc Function will first copy the parameter value to defer The location next to the memory address value is used as a parameter , If the pointer is passed, the pointer will be copied directly , Value passing copies the value directly to defer Location of parameters .
And then in the deferreturn Function will copy the parameter values to the stack , And then call jmpdefer To perform .
func deferreturn(arg0 uintptr) {
...
switch d.siz {
case 0:
// Do nothing.
case sys.PtrSize:
// take defer Copy the saved parameters
// arg0 It's actually caller SP Top of stack address value , So here you're actually copying the parameters to caller SP Top of stack address value
(uintptr)(unsafe.Pointer(&arg0)) = (uintptr)(deferArgs(d))
default:
// If the parameter size is not sys.PtrSize, So make a data copy
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
...
}
- Multiple defer How statements are executed ?
Calling deferproc Function registration defer The new element will be inserted in the header when it is used , When executing, it also obtains the chain header and executes in turn .
- defer、return、 What is the execution order of the return value ?
For this question , Let's take the assembly of the output in the above example and study it :
"".f STEXT size=126 args=0x8 locals=0x20
...
0x004e 00078 (main.go:11) MOVQ $6, "".result+40(SP) ;; Constant 6 write in 40(SP), As return value
0x0057 00087 (main.go:11) XCHGL AX, AX
0x0058 00088 (main.go:11) CALL runtime.deferreturn(SB) ;; call runtime.deferreturn function
0x005d 00093 (main.go:11) MOVQ 24(SP), BP
0x0062 00098 (main.go:11) ADDQ $32, SP
0x0066 00102 (main.go:11) RET
You can see from this compilation that , about
- The first is to set the return value to constant first 6;
- And then call
runtime.deferreturnperformdeferLinked list ; - perform RET The command jumps to caller function ;
On the stack
I talked about it at the beginning , stay Go Of 1.13 After the release, we added defer On the stack of , So one difference from heap allocation is that it's created on the stack defer It's through deferprocStack To create .
Go At compile time in SSA The stage will be judged , If it's stack allocation , Then you need to use the compiler directly on the function call frame to initialize _defer Record , And pass it as a parameter to deferprocStack. There is no difference between other execution processes and heap allocation .
about deferprocStack Function let's take a brief look at :
file location :src/cmd/compile/internal/gc/ssa.go
func deferprocStack(d *_defer) {
gp := getg()
if gp.m.curg != gp {
throw("defer on system stack")
}
d.started = false
d.heap = false // Distributed on the stack _defer
d.openDefer = false
d.sp = getcallersp()
d.pc = getcallerpc()
d.framepc = 0
d.varp = 0
*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
*(*uintptr)(unsafe.Pointer(&d.fd)) = 0
// Will be multiple _defer Records are concatenated through linked lists
*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))
return0()
} The main function is to give _defer Structure assignment , And back to .
Open code
Go Language in 1.14 Through code inline optimization , Make the end of the function directly to defer Function to call , There's almost no extra cost to do it . stay SSA The construction phase of buildssa It will check whether the conditions are met according to , Open coding will be inserted only if the conditions are met , because SSA The code in the build phase of is not easy to understand , So here's just the basic principle , No code analysis .
We can assemble and print the example of allocation on the heap :
$ GOOS=linux GOARCH=amd64 go tool compile -S main.go
"".f STEXT size=155 args=0x8 locals=0x30
0x0000 00000 (main.go:7) TEXT "".f(SB), ABIInternal, $48-8
...
0x002e 00046 (main.go:7) MOVQ $0, "".~r0+56(SP)
0x0037 00055 (main.go:8) MOVQ $100, "".i+16(SP)
0x0040 00064 (main.go:9) LEAQ "".f.func1·f(SB), AX
0x0047 00071 (main.go:9) MOVQ AX, ""..autotmp_4+32(SP)
0x004c 00076 (main.go:9) LEAQ "".i+16(SP), AX
0x0051 00081 (main.go:9) MOVQ AX, ""..autotmp_5+24(SP)
0x0056 00086 (main.go:9) MOVB $1, ""..autotmp_3+15(SP)
0x005b 00091 (main.go:12) MOVQ "".i+16(SP), AX
0x0060 00096 (main.go:12) MOVQ AX, "".~r0+56(SP)
0x0065 00101 (main.go:12) MOVB $0, ""..autotmp_3+15(SP)
0x006a 00106 (main.go:12) MOVQ ""..autotmp_5+24(SP), AX
0x006f 00111 (main.go:12) MOVQ AX, (SP)
0x0073 00115 (main.go:12) PCDATA $1, $1
0x0073 00115 (main.go:12) CALL "".f.func1(SB) ;; Call directly defer function
0x0078 00120 (main.go:12) MOVQ 40(SP), BP
0x007d 00125 (main.go:12) ADDQ $48, SP
0x0081 00129 (main.go:12) RETWe can see that in the assembly output above, we directly put defer The function is inserted at the end of the function and called .
The example above is easy to optimize , But if one defer In a conditional statement , This condition cannot be determined until runtime , So how to optimize it ?
It's also used in open coding defer bit Delay bit to determine whether the conditional branch should be executed . This The delay bit length is a 8 Bit binary code , So in this optimization, you can only use at most 8 individual defer, Including in the conditional judgment defer. Whether each bit is set to 1, To determine whether the delay statement is set at run time , If you set , The call . Otherwise... Is not called .
For example, an example is explained in the following article :
https://go.googlesource.com/proposal/+/refs/heads/master/design/34481-opencoded-defers.md
defer f1(a)
if cond {
defer f2(b)
}
body...In the phase of creating deferred calls , First of all, the conditional ones are recorded by the specific position of the delay bit defer Be triggered .
deferBits := 0 // Initial value 00000000
deferBits |= 1 << 0 // The first one defer, Set to 00000001
_f1 = f1
_a1 = a1
if cond {
// If the second one defer Set up , Is set to 00000011, Otherwise, it will still be 00000001
deferBits |= 1 << 1
_f2 = f2
_a2 = a2
} Before the function returns and exits , exit The function creates a check code for the delay bits in reverse order :
exit:
// Judge deferBits & 00000010 == 00000010 Is it true
if deferBits & 1<<1 != 0 {
deferBits &^= 1<<1
tmpF2(tmpB)
}
// Judge deferBits & 00000001 == 00000001 Is it true
if deferBits & 1<<0 != 0 {
deferBits &^= 1<<0
tmpF1(tmpA)
} Before the function exits, it will judge whether the delay bit is equal to the value of the corresponding position 1, If 1, So it means that the defer function .
summary
This article mainly explains defer Implementation rules of , And right defer Types are introduced . Mainly through the heap allocation to explain defer How function calls are done , Such as : Function calls are used to understand " defer Parameter transfer of "、" Multiple defer How statements are executed "、" as well as defer、return、 What is the execution order of the return value " And so on . Through this analysis, I hope you can understand defer Can have a deeper understanding of .
Reference
Delay statement https://golang.design/under-the-hood/zh-cn/part1basic/ch03lang/defer/
defer https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/#53-defer
Defer statements https://golang.org/ref/spec#Defer_statements
Proposal: Low-cost defers through inline code, and extra funcdata to manage the panic case https://go.googlesource.com/proposal/+/refs/heads/master/design/34481-opencoded-defers.md
Defer in Practice https://exlskills.com/learn-en/courses/aap-learn-go-golang--learn_golang_asap/aap-learn--asapgo/beyond-the-basics-YvUaZMowIAIo/defer-aqcApCogZxuV
Completely transformed defer https://mp.weixin.qq.com/s/gaC2gmFhJezH-9-uxpz07w
Go defer In depth analysis (3)—— Source code analysis , Deep principle analysis https://zhuanlan.zhihu.com/p/351177696
Inlined defers in Go https://rakyll.org/inlined-defers/
Go How a program is compiled into object machine code https://segmentfault.com/a/1190000016523685
边栏推荐
- Reference to junit5 test framework in gradle
- Is Guotai Junan Futures safe? How to open a futures account? How to reduce the futures commission?
- Install the imagemagick7.1 library and the imageick extension for PHP
- [C language questions -- leetcode 12 questions] take you off and fly into the garbage
- Wechat official account debugging and natapp environment building
- Rush for IPO, Hello, I'm in a hurry
- MySQL Advanced Series: locks - locks in InnoDB
- 企业安全攻击面分析工具
- Istio FAQ: return 426 status code
- Enterprise security attack surface analysis tool
猜你喜欢
MySQL Advanced Series: Locks - Locks in InnoDB
MySQL進階系列:鎖-InnoDB中鎖的情况
![[download attached] installation and simple use of Chinese version of awvs](/img/3b/f26617383690c86edff465c9a1099e.png)
[download attached] installation and simple use of Chinese version of awvs

一文理解OpenStack网络

Build go command line program tool chain

Some adventurer hybrid versions with potential safety hazards will be recalled

Solution of intelligent all in one machine in expressway service area

微信公众号调试与Natapp环境搭建

Apple is no match for the longest selling mobile phone made in China, and has finally brought back the face of the domestic mobile phone

The equipment is connected to the easycvr platform through the national standard gb28181. How to solve the problem of disconnection?
随机推荐
One article explains Jackson configuration information in detail
Software test [high frequency] interview questions sorted out by staying up late (latest in 2022)
Some experiences of project K several operations in the global template
The catch-up of domestic chips has scared Qualcomm, the leader of mobile phone chips in the United States, and made moves to cope with the competition
sql 多表更新数据非常慢
Goby+awvs realize attack surface detection
Cap: multiple attention mechanism, interesting fine-grained classification scheme | AAAI 2021
Solution to the problem that FreeRTOS does not execute new tasks
Global and Chinese market of inverted syrup 2022-2028: Research Report on technology, participants, trends, market size and share
安装ImageMagick7.1库以及php的Imagick扩展
B. Ternary Sequence(思维+贪心)Codeforces Round #665 (Div. 2)
How does the effective date of SAP PP ECM affect the work order?
MySQL timestamp format conversion date format string
Remote connection raspberry pie in VNC Viewer Mode
一文详解JackSon配置信息
国泰君安期货安全么?期货开户怎么开?期货手续费怎么降低?
日志记录真没你想的那么简单
Global and Chinese markets of Leyte coin exchange 2022-2028: Research Report on technology, participants, trends, market size and share
C. K-th Not Divisible by n(数学+思维) Codeforces Round #640 (Div. 4)
2021-05-03: given a non negative integer num, how to avoid circular statements,