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.