doubt
The functions of the Go language support multiple return values.
Just inside the function itself, assign to the return value, and then return returns all of the returned values.
Recently, when writing code, I often find that after a return, I have to defer to do some finishing work, such as committing or rolling back the transaction. So I want to know exactly what the relationship between this return and defer is, and which one comes first and what impact does it have on the final return value?
To verify the
The problem is more complicated than I thought, so let’s look at the output of this code
package main
import "fmt"
func main(a) {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1(a) int {
var i int
defer func(a) {
i++
fmt.Println("f11: ", i)
}()
defer func(a) {
i++
fmt.Println("f12: ", i)
}()
i = 1000
return i
}
func f2(a) (i int) {
defer func(a) {
i++
fmt.Println("f21: ", i)
}()
defer func(a) {
i++
fmt.Println("f22: ", i)
}()
i = 1000
return i
}
Copy the code
The final result is as follows
f12: 1001
f11: 1002
f1 result: 1000
f22: 1001
f21: 1002
f2 result: 1002
Copy the code
F1 function:
If there is no assignment, it initializes to 0. Then it assigns I =1000. Then it executes a return statement that returns the value of I.
The defer function part is also executed before actually returning, and the two defer functions increment for I, which is 1001 and 1002, respectively
F2 function:
I’m going to go into this function, because I’ve defined the return variable to be I, and I’m going to assign I =1000, and I’m going to return the value of I.
Again, you execute two defer functions before you actually return I, and again I increments to 1001 and 1002.
The crux of the matter is why the nameless parameter returns a value of 1000, which has not been affected by the I increment of the defer function; The named function, after defer, finally returns an I value of 1002.
Found some reasons on the Internet, mentioned a conclusion
The reason is thatreturnThe return value is saved, and for the nameless return value, it is saved in a temporary object, which deferred cannot see; For named return values, they are stored in named variables.Copy the code
Seeing this conclusion, I wanted to see if I could get some clues by printing the address value of I
To do this, I added the address information to print I in both functions
package main
import "fmt"
func main(a) {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1(a) int {
var i int
fmt.Printf("i: %p \n", &i)
defer func(a) {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f11: ", i)
}()
defer func(a) {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f12: ", i)
}()
i = 1000
return i
}
func f2(a) (i int) {
fmt.Printf("i: %p \n", &i)
defer func(a) {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f21: ", i)
}()
defer func(a) {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f22: ", i)
}()
i = 1000
return i
}
Copy the code
The program output is
i: 0xc000090000
i: 0xc000090000
f12: 1001
i: 0xc000090000
f11: 1002
f1 result: 1000
i: 0xc00009a008
i: 0xc00009a008
f22: 1001
i: 0xc00009a008
f21: 1002
f2 result: 1002
Copy the code
It can be seen from this result that the address of variable I has not changed in the whole process in either f1 or F2 function.
So I seem to understand the above conclusion, but it’s still a bit vague. The return is saved in a temporary object and defer can’t see the temporary variable. But why can the value of I be accumulated on the basis of 1000?
clearer
If you want to solve this question from the root, it is best to be able to look at the execution of this program, behind the memory allocation.
At this point, I thought of a few days ago in the book can be command to go language into assembly language.
To simplify things, change the source code to
package main
import "fmt"
func main(a) {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1(a) int {
var i int
defer func(a) {
i++
fmt.Println("f11: ", i)
}()
i = 1000
return i
}
func f2(a) (i int) {
defer func(a) {
i++
fmt.Println("f21: ", i)
}()
i = 1000
return i
}
Copy the code
Run the go tool compile -s test.go command to obtain the assembly code as follows
os.(*File).close STEXT dupok nosplit size=26 args=0x18 locals=0x0
...
0x0000 00000 (test.go:5) TEXT "".main(SB), ABIInternal, The $136-0
0x0000 00000 (test.go:5) MOVQ (TLS), CX
0x0009 00009 (test.go:5) LEAQ -8(SP), AX
0x000e 00014 (test.go:5) CMPQ AX, 16(CX)
0x0012 00018 (test.go:5) JLS 315
0x0018 00024 (test.go:5) SUBQ The $136, SP
0x001f 00031 (test.go:5) MOVQ BP, 128(SP)
0x0027 00039 (test.go:5) LEAQ 128(SP), BP
0x002f 00047 (test.go:5) FUNCDATA $0, gclocals 7 d2d5fca80364273fb07d5820a76fef4 (SB)..."".f1 STEXT size=145 args=0x8 locals=0x28
0x0000 00000 (test.go:10) TEXT "".f1(SB), ABIInternal, $40-8
0x0000 00000 (test.go:10) MOVQ (TLS), CX
0x0009 00009 (test.go:10) CMPQ SP, 16(CX)
0x000d 00013 (test.go:10) JLS 135
0x000f 00015 (test.go:10) SUBQ $40, SP
0x0013 00019 (test.go:10) MOVQ BP, 32(SP)
0x0018 00024 (test.go:10) LEAQ 32(SP), BP
0x001d 00029 (test.go:10) FUNCDATA $033, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 10)The $133, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 10)$39, gclocals fb7f0986f647f17cb53dda1484e0f7a (SB) 0 x001d 00029 (test. Go: 10) PCDATA$2.$0
0x001d 00029 (test.go:10) PCDATA $0.$0
0x001d 00029 (test.go:10) MOVQ $0."".~r0+48(SP)
0x0026 00038 (test.go:11) MOVQ $0."".i+24(SP)
0x002f 00047 (test.go:12) MOVL $8, (SP)
0x0036 00054 (test.go:12) PCDATA $2.The $1
0x0036 00054 (test.go:12) LEAQ ""Func1 ·f(SB), AX 0x003D 00061 (test.go:12) PCDATA$2.$0
0x003d 00061 (test.go:12) MOVQ AX, 8(SP)
0x0042 00066 (test.go:12) PCDATA $2.The $1
0x0042 00066 (test.go:12) LEAQ "".i+24(SP), AX
0x0047 00071 (test.go:12) PCDATA $2.$0
0x0047 00071 (test.go:12) MOVQ AX, 16(SP)
0x004c 00076 (test.go:12) CALL runtime.deferproc(SB)
0x0051 00081 (test.go:12) TESTL AX, AX
0x0053 00083 (test.go:12) JNE 119
0x0055 00085 (test.go:17) MOVQ The $1000."".i+24(SP)
0x005e 00094 (test.go:18) MOVQ The $1000."".~r0+48(SP)
0x0067 00103 (test.go:18) XCHGL AX, AX
0x0068 00104 (test.go:18) CALL runtime.deferreturn(SB)
0x006d 00109 (test.go:18) MOVQ 32(SP), BP
0x0072 00114 (test.go:18) ADDQ $40, SP
0x0076 00118 (test.go:18) RET
0x0077 00119 (test.go:12) XCHGL AX, AX
0x0078 00120 (test.go:12) CALL runtime.deferreturn(SB)
0x007d 00125 (test.go:12) MOVQ 32(SP), BP
0x0082 00130 (test.go:12) ADDQ $40, SP
0x0086 00134 (test.go:12) RET
0x0087 00135 (test.go:12) NOP
0x0087 00135 (test.go:10) PCDATA $0, $-1
0x0087 00135 (test.go:10) PCDATA $2, $-1
0x0087 00135 (test.go:10) CALL runtime.morestack_noctxt(SB)
0x008c 00140 (test.go:10) JMP 0
...
0x0000 00000 (test.go:21) TEXT "".f2(SB), ABIInternal, $32-8
0x0000 00000 (test.go:21) MOVQ (TLS), CX
0x0009 00009 (test.go:21) CMPQ SP, 16(CX)
0x000d 00013 (test.go:21) JLS 117
0x000f 00015 (test.go:21) SUBQ $32, SP
0x0013 00019 (test.go:21) MOVQ BP, 24(SP)
0x0018 00024 (test.go:21) LEAQ 24(SP), BP
0x001d 00029 (test.go:21) FUNCDATA $033, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 21)The $133, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 21)$39, gclocals fb7f0986f647f17cb53dda1484e0f7a (SB) 0 x001d 00029 (test. Go: 21) PCDATA$2.$0
0x001d 00029 (test.go:21) PCDATA $0.$0
0x001d 00029 (test.go:21) MOVQ $0."".i+40(SP)
0x0026 00038 (test.go:22) MOVL $8, (SP)
0x002d 00045 (test.go:22) PCDATA $2.The $1
0x002d 00045 (test.go:22) LEAQ ""Func1 ·f(SB), AX 0x0034 00052 (test.go:22) PCDATA$2.$0
0x0034 00052 (test.go:22) MOVQ AX, 8(SP)
0x0039 00057 (test.go:22) PCDATA $2.The $1
0x0039 00057 (test.go:22) LEAQ "".i+40(SP), AX
0x003e 00062 (test.go:22) PCDATA $2.$0
0x003e 00062 (test.go:22) MOVQ AX, 16(SP)
0x0043 00067 (test.go:22) CALL runtime.deferproc(SB)
0x0048 00072 (test.go:22) TESTL AX, AX
0x004a 00074 (test.go:22) JNE 101
0x004c 00076 (test.go:26) MOVQ The $1000."".i+40(SP)
0x0055 00085 (test.go:27) XCHGL AX, AX
0x0056 00086 (test.go:27) CALL runtime.deferreturn(SB)
0x005b 00091 (test.go:27) MOVQ 24(SP), BP
0x0060 00096 (test.go:27) ADDQ $32, SP
0x0064 00100 (test.go:27) RET
0x0065 00101 (test.go:22) XCHGL AX, AX
0x0066 00102 (test.go:22) CALL runtime.deferreturn(SB)
0x006b 00107 (test.go:22) MOVQ 24(SP), BP
0x0070 00112 (test.go:22) ADDQ $32, SP
0x0074 00116 (test.go:22) RET
0x0075 00117 (test.go:22) NOP
0x0075 00117 (test.go:21) PCDATA $0, $-1
0x0075 00117 (test.go:21) PCDATA $2, $-1 0x0075 00117 (test.go:21) CALL runtime.morestack_noctxt(SB) 0x007a 00122 (test.go:21) JMP 0 ... . rel 16+8 t=1 type.[2]interface {}+0Copy the code
Return = null; return = null; return = null; return = null; return = null
But, the tricky part is, I didn’t learn assembly.
Since the execution results of the two functions are different, there must be some differences in the assembly level, so I started to look for the differences, and finally found the key information in the above assembly code as follows
"".f2 STEXT size=124 args=0x8 locals=0x20
0x0000 00000 (test.go:21) TEXT "".f2(SB), ABIInternal, $32-8
0x0000 00000 (test.go:21) MOVQ (TLS), CX
0x0009 00009 (test.go:21) CMPQ SP, 16(CX)
0x000d 00013 (test.go:21) JLS 117
0x000f 00015 (test.go:21) SUBQ $32, SP
0x0013 00019 (test.go:21) MOVQ BP, 24(SP)
0x0018 00024 (test.go:21) LEAQ 24(SP), BP
0x001d 00029 (test.go:21) FUNCDATA $033, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 21)The $133, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 21)$39, gclocals fb7f0986f647f17cb53dda1484e0f7a (SB) 0 x001d 00029 (test. Go: 21) PCDATA$2.$0
0x001d 00029 (test.go:21) PCDATA $0.$0
0x001d 00029 (test.go:21) MOVQ $0."".i+40(SP)
0x0026 00038 (test.go:22) MOVL $8, (SP)
0x002d 00045 (test.go:22) PCDATA $2.The $1
0x002d 00045 (test.go:22) LEAQ ""Func1 ·f(SB), AX 0x0034 00052 (test.go:22) PCDATA$2.$0
0x0034 00052 (test.go:22) MOVQ AX, 8(SP)
0x0039 00057 (test.go:22) PCDATA $2.The $1
0x0039 00057 (test.go:22) LEAQ "".i+40(SP), AX
0x003e 00062 (test.go:22) PCDATA $2.$0
0x003e 00062 (test.go:22) MOVQ AX, 16(SP)
0x0043 00067 (test.go:22) CALL runtime.deferproc(SB)
0x0048 00072 (test.go:22) TESTL AX, AX
0x004a 00074 (test.go:22) JNE 101
0x004c 00076 (test.go:26) MOVQ The $1000."".i+40(SP)
0x0055 00085 (test.go:27) XCHGL AX, AX
0x0056 00086 (test.go:27) CALL runtime.deferreturn(SB)
0x005b 00091 (test.go:27) MOVQ 24(SP), BP
0x0060 00096 (test.go:27) ADDQ $32, SP
0x0064 00100 (test.go:27) RET
0x0065 00101 (test.go:22) XCHGL AX, AX
0x0066 00102 (test.go:22) CALL runtime.deferreturn(SB)
0x006b 00107 (test.go:22) MOVQ 24(SP), BP
0x0070 00112 (test.go:22) ADDQ $32, SP
0x0074 00116 (test.go:22) RET
0x0075 00117 (test.go:22) NOP
0x0075 00117 (test.go:21) PCDATA $0, $-1
0x0075 00117 (test.go:21) PCDATA $2, $-1
0x0075 00117 (test.go:21) CALL runtime.morestack_noctxt(SB)
0x007a 00122 (test.go:21) JMP 0
Copy the code
This is key information about the return value of the f2 name
0x004c 00076 (test.go:26) MOVQ The $1000."".i+40(SP)
Copy the code
I +40(SP). I +40(SP)
"".f1 STEXT size=145 args=0x8 locals=0x28
0x0000 00000 (test.go:10) TEXT "".f1(SB), ABIInternal, $40-8
0x0000 00000 (test.go:10) MOVQ (TLS), CX
0x0009 00009 (test.go:10) CMPQ SP, 16(CX)
0x000d 00013 (test.go:10) JLS 135
0x000f 00015 (test.go:10) SUBQ $40, SP
0x0013 00019 (test.go:10) MOVQ BP, 32(SP)
0x0018 00024 (test.go:10) LEAQ 32(SP), BP
0x001d 00029 (test.go:10) FUNCDATA $033, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 10)The $133, gclocals cdeccccebe80329f1fdbee7f5874cb (SB) 0 x001d 00029 FUNCDATA (test. Go: 10)$39, gclocals fb7f0986f647f17cb53dda1484e0f7a (SB) 0 x001d 00029 (test. Go: 10) PCDATA$2.$0
0x001d 00029 (test.go:10) PCDATA $0.$0
0x001d 00029 (test.go:10) MOVQ $0."".~r0+48(SP)
0x0026 00038 (test.go:11) MOVQ $0."".i+24(SP)
0x002f 00047 (test.go:12) MOVL $8, (SP)
0x0036 00054 (test.go:12) PCDATA $2.The $1
0x0036 00054 (test.go:12) LEAQ ""Func1 ·f(SB), AX 0x003D 00061 (test.go:12) PCDATA$2.$0
0x003d 00061 (test.go:12) MOVQ AX, 8(SP)
0x0042 00066 (test.go:12) PCDATA $2.The $1
0x0042 00066 (test.go:12) LEAQ "".i+24(SP), AX
0x0047 00071 (test.go:12) PCDATA $2.$0
0x0047 00071 (test.go:12) MOVQ AX, 16(SP)
0x004c 00076 (test.go:12) CALL runtime.deferproc(SB)
0x0051 00081 (test.go:12) TESTL AX, AX
0x0053 00083 (test.go:12) JNE 119
0x0055 00085 (test.go:17) MOVQ The $1000."".i+24(SP)
0x005e 00094 (test.go:18) MOVQ The $1000."".~r0+48(SP)
0x0067 00103 (test.go:18) XCHGL AX, AX
0x0068 00104 (test.go:18) CALL runtime.deferreturn(SB)
0x006d 00109 (test.go:18) MOVQ 32(SP), BP
0x0072 00114 (test.go:18) ADDQ $40, SP
0x0076 00118 (test.go:18) RET
0x0077 00119 (test.go:12) XCHGL AX, AX
0x0078 00120 (test.go:12) CALL runtime.deferreturn(SB)
0x007d 00125 (test.go:12) MOVQ 32(SP), BP
0x0082 00130 (test.go:12) ADDQ $40, SP
0x0086 00134 (test.go:12) RET
0x0087 00135 (test.go:12) NOP
0x0087 00135 (test.go:10) PCDATA $0, $-1
0x0087 00135 (test.go:10) PCDATA $2, $-1
0x0087 00135 (test.go:10) CALL runtime.morestack_noctxt(SB)
0x008c 00140 (test.go:10) JMP 0
Copy the code
This is the key information of the f1 unknown return value, mainly see
0x0055 00085 (test.go:17) MOVQ The $1000."".i+24(SP)
0x005e 00094 (test.go:18) MOVQ The $1000."".~r0+48(SP)
Copy the code
I +24(SP), and then I assign 1000 to “”.~r0+48(SP). We find the verification here in response to the previous conclusion. I +24(SP) instead of reading the value of the temporary space, the subsequent defer read the memory address “”. I +24(SP). R0 +48(SP) = 1000 (Since no compilations have been studied, some details may be subject to verification)
conclusion
At this point, we’ve understood the subtle relationship between return and defer in Go, and we’ve seen the difference at the assembly level between nameless and named return values.