сомневаться
В предыдущей главе, посвященной функциям, было сказано, что функции языка Go поддерживают множественные возвращаемые значения.
Пока возвращаемое значение присваивается в теле функции, а return добавляется в конце, все возвращаемые значения могут быть возвращены.
При написании кода в последнее время часто приходится сталкиваться с тем, что после возврата некоторые завершающие работы должны быть выполнены в отсрочке, например, отправка транзакции или откат. Итак, я хочу выяснить, какова связь между возвратом и отсрочкой, кто на первом месте и как это влияет на итоговое возвращаемое значение?
практическая проверка
После понимания проблема оказалась сложнее, чем я думал.Если вы мне не верите, взгляните на вывод следующего кода.
package main
import "fmt"
func main() {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1() int {
var i int
defer func() {
i++
fmt.Println("f11: ", i)
}()
defer func() {
i++
fmt.Println("f12: ", i)
}()
i = 1000
return i
}
func f2() (i int) {
defer func() {
i++
fmt.Println("f21: ", i)
}()
defer func() {
i++
fmt.Println("f22: ", i)
}()
i = 1000
return i
}
Окончательный результат выполнения следующий
f12: 1001
f11: 1002
f1 result: 1000
f22: 1001
f21: 1002
f2 result: 1002
функция f1:
При входе в эту функцию, поскольку переменная возвращаемого значения не указана, необходимо сначала объявить переменную i, так как она имеет тип int.Если присваивания нет, переменная инициализируется значением 0, а затем выполняется операция присваивания i= 1000 выполняется, а затем выполняется оператор return для возврата значения i value.
Перед фактическим возвратом также выполняется часть функции отсрочки Две функции отсрочки выполняют операции автоинкремента для i соответственно, а значения i равны 1001 и 1002 по очереди.
функция f2:
Введите эту функцию, потому что переменная возвращаемого значения была определена как i, а затем напрямую присвойте i=1000, а затем верните значение i.
Точно так же перед фактическим возвратом i выполняются две функции отсрочки, и i увеличивается до 1001 и 1002 по очереди.
Суть проблемы в том, почему значение, возвращаемое безымянным параметром, равно 1000, на которое не влияет функция отсрочки на автоинкремент i, а названная функция возвращает 1002 после выполнения функции отсрочки.
Я нашел несколько причин в Интернете и упомянул вывод
原因就是return会将返回值先保存起来,对于无名返回值来说, 保存在一个临时对象中,defer是看不到这个临时对象的; 而对于有名返回值来说,就保存在已命名的变量中。
Увидев этот вывод, я хочу попытаться посмотреть, можно ли увидеть некоторые подсказки и подсказки, распечатав значение адреса i
Для этого к двум функциям добавляется информация об адресе печати i.
package main
import "fmt"
func main() {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1() int {
var i int
fmt.Printf("i: %p \n", &i)
defer func() {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f11: ", i)
}()
defer func() {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f12: ", i)
}()
i = 1000
return i
}
func f2() (i int) {
fmt.Printf("i: %p \n", &i)
defer func() {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f21: ", i)
}()
defer func() {
i++
fmt.Printf("i: %p \n", &i)
fmt.Println("f22: ", i)
}()
i = 1000
return i
}
Вывод программы
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
Из этого результата видно, что адрес переменной i не изменился за весь процесс ни функции f1, ни функции f2.
Так что вышеприведённый вывод я вроде понял, но всё же немного расплывчато, return хранится во временном объекте, и defer не может видеть эту временную переменную, но почему значение i может накапливаться на основе 1000?
очистить облака
Если вы хотите принципиально решить этот вопрос, лучше всего посмотреть, как выделяется память для выполнения этой программы.
В это время я вспомнил тот факт, что несколько дней назад упоминал в книге, что язык go можно преобразовать в язык ассемблера с помощью команд.
Чтобы упростить задачу, измените исходный код на
package main
import "fmt"
func main() {
fmt.Println("f1 result: ", f1())
fmt.Println("f2 result: ", f2())
}
func f1() int {
var i int
defer func() {
i++
fmt.Println("f11: ", i)
}()
i = 1000
return i
}
func f2() (i int) {
defer func() {
i++
fmt.Println("f21: ", i)
}()
i = 1000
return i
}
Код сборки, полученный при выполнении команды go tool compile -S test.go, выглядит следующим образом
os.(*File).close STEXT dupok nosplit size=26 args=0x18 locals=0x0
...
0x0000 00000 (test.go:5) TEXT "".main(SB), ABIInternal, $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 $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·7d2d5fca80364273fb07d5820a76fef4(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 $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:10) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:10) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
0x001d 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, $1
0x0036 00054 (test.go:12) LEAQ "".f1.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, $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 $1000, "".i+24(SP)
0x005e 00094 (test.go:18) MOVQ $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 $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:21) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:21) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
0x001d 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, $1
0x002d 00045 (test.go:22) LEAQ "".f2.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, $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 $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 {}+0
Я чувствую, что это всего лишь один шаг от истины. Только после прочтения этого ассемблерного кода я могу понять, что делает этот возврат, когда он возвращает безымянное и именованное возвращаемое значение. Как распределяется так называемая переменная нулевого времени? Меня немного возбуждает мысль об этом.
Однако самое сложное в том, что я не изучил ассемблер -_-!
Но опять же, какое это имеет значение, поскольку результаты выполнения двух функций разные, должны быть различия на уровне сборки, поэтому я начал искать различия и, наконец, нашел ключевую информацию в приведенном выше ассемблерном коде следующим образом.
"".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 $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:21) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:21) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
0x001d 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, $1
0x002d 00045 (test.go:22) LEAQ "".f2.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, $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 $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
Это ключевая информация известного возвращаемого значения f2, в основном см.
0x004c 00076 (test.go:26) MOVQ $1000, "".i+40(SP)
Вероятно, это означает поставить 1000 на адрес памяти "".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 $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:10) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (test.go:10) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
0x001d 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, $1
0x0036 00054 (test.go:12) LEAQ "".f1.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, $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 $1000, "".i+24(SP)
0x005e 00094 (test.go:18) MOVQ $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
Это ключевая информация о безымянном возвращаемом значении f1, в основном см.
0x0055 00085 (test.go:17) MOVQ $1000, "".i+24(SP)
0x005e 00094 (test.go:18) MOVQ $1000, "".~r0+48(SP)
Вероятно, это означает, что 1000 помещается в адрес памяти ".i+24(SP), а затем 1000 присваивается "".~r0+48(SP), что отличается от f1. Соответствующее предыдущему заключению, мы находим здесь подтверждение. Общий процесс - это случай безымянного возвращаемого значения. При возврате открывается новое пространство памяти. Последующая отсрочка считывает адрес памяти, такой как "".i+24(SP) и не может прочитать значение временного пространства. . Возврат в конце функции также является значением, соответствующим "".~r0+48(SP), что равно 1000. (Поскольку компиляция не изучалась, некоторые детали могут нуждаться в проверке)
в заключении
На этом этапе мы можем понять тонкую взаимосвязь между return и defer в языке Go и увидеть разницу между безымянным возвращаемым значением и именованным возвращаемым значением, возвращаемым с уровня сборки.