go - 混合打印和 fmt.Println 和堆栈增长
问题描述
我正在看关于围棋的讲座。一个关于堆栈增长的例子:
package main
import "fmt"
const size = 1024
func main() {
fmt.Println("Start")
s:= "HELLO"
stackCopy(&s, 0, [size]int{})
}
func stackCopy(s *string, c int, a [size]int){
println("println: ", s, *s)
//fmt.Println("fmt: ", s, *s)
c++
if c == 10 {
return
}
stackCopy(s, c, a)
}
仅使用 println 时,“s”的地址会发生变化(堆栈正在增长并且数据移动到其他地方):
Start
println: 0xc000107f58 HELLO
println: 0xc000107f58 HELLO
println: 0xc000117f58 HELLO
println: 0xc000117f58 HELLO
println: 0xc000117f58 HELLO
println: 0xc000117f58 HELLO
println: 0xc00019ff58 HELLO
println: 0xc00019ff58 HELLO
println: 0xc00019ff58 HELLO
println: 0xc00019ff58 HELLO
当我混合 println 和 fmt.Println 或只有“s”的 fmt.Println 地址不会改变时:
Start
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
println: 0xc00010a040 HELLO
fmt: 0xc00010a040 HELLO
为什么会这样?
解决方案
区别在于使用内置 println()
函数和标准 libfmt.Println()
中的函数。
println()
是一个内置函数,编译器知道println()
不保留传递给它的任何参数,因此传递给它的参数不会转义到堆。
fmt.Prinln()
来自标准库,它作为您的任何自定义函数处理:编译器不假设它不保留传递的参数,因此您传递给它的参数可能会逃逸到堆,因此它们被分配在堆上而不是在堆栈上。
这是一个重要的区别。接下来导致偏差的原因是 Go 具有动态堆栈:它从小开始,如果需要,它可以增长。有关详细信息,请参阅Go 是否具有“无限调用堆栈”等价物?由于你向递归函数传递了相当大的参数stackCopy()
(大小为 1024 的数组,元素类型int
为 4 或 8 字节),初始的小堆栈将不足,将分配更大的堆栈,导致堆栈分配的变量被移动,因此他们的地址改变了。使用时不会发生这种情况fmt.Println()
,因为编译器推断s
可能会逃逸,因此它是在堆上分配的,并且增加堆栈不会导致移动s
.
您可以使用这些-gcflags '-m'
标志来打印编译器的转义分析。
在第一种情况下:
./play.go:8:13: inlining call to fmt.Println
./play.go:13:16: s does not escape
./play.go:8:14: "Start" escapes to heap
./play.go:8:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape
这里重要的是:s does not escape
.
在第二种情况下:
./play.go:15:13: inlining call to fmt.Println
./play.go:8:13: inlining call to fmt.Println
./play.go:13:16: leaking param: s
./play.go:15:14: "fmt: " escapes to heap
./play.go:15:30: *s escapes to heap
./play.go:15:13: []interface {} literal does not escape
./play.go:9:2: moved to heap: s
./play.go:8:14: "Start" escapes to heap
./play.go:8:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape
如您所见,编译器推断这s
是一个泄漏参数,*s
逃逸到堆。
推荐阅读
- google-tag-manager - 正则表达式表查找不适用于 Click Element 变量
- python - 是否可以在 Python 中使用连字符/破折号作为变量
- boolean - 如果第一个返回 false,布尔和语句停止的词是什么?
- r - 为回归创建基线变量
- python - 加载到终端和 Pycharm 中的不同环境变量
- javascript - 如何在前端仅使用 javascript 将图像、文档、音频或视频转换为其他文件格式?
- python - 根据用户输入的列和行填充随机数的数据框
- firebase - 使用 Firebase Hosting + Cloud Run Express 服务器时在哪里设置 Cache-Control?
- c# - ASP.Net Core Error CS1061是什么问题
- python - 如何将 matplotlib 图表迁移到 hvplot?