面试的时候面试官就喜欢问一个问题 为什么go语音不分堆和栈
屁话 go语音是分堆和栈的
只是go语音开发者,用内存逃逸机制 , 使得大家认为go语音不分堆和栈
开始教学
内存中分为堆和栈:
我们定义的变量会分配堆或者栈上:
具体有一些规则:
例如c语言:
-
new关键字,那么就一定分配在堆上
-
堆上面的数据,如果不手动释放,会与程序共存亡(内存泄露)
-
栈上的数据,会有自己的作用域,只要退出作用域,系统自动释放。
go语言有gc机制,可以自动回收内存。
go语言在编译的时候,就会确定一个变量是否分配在堆上还是栈上。规则如下:
-
如果变量在其他地方(非局部)被引用,那么就一定分配在堆上,否则分配在栈上。
-
即使没有被外部引用,但是对象过大,无法存在栈上,那么就分配在堆上。
什么是逃逸分析
变量分配在堆上,不分配在栈上,这种现象叫做内存逃逸。
案例分析
案例一:被局部变量引用时逃逸
test1.go
package main
type People struct {
Name string
Age int
}
func getInfo() *People {
//perple在函数内部创建,属于局部变量,但是在getInfo函数之外引用了
//此时,编译器会将people变量分配到堆上
return &People{
Name: "Lily",
Age: 20,
}
}
func main() {
_ = getInfo()
}
方式1:
go build --gcflags "-m -m -l" test1.go
方式2:
go tool compile -S test1.go
案例二:未确定类型时逃逸
package main
import "fmt"
func main() {
//使用new关键字,也不一定分配到堆上
//由于在main之外没有使用过str这个变量,所有分配到栈上
str := new(string)
*str = "hello"
}
duke@DUKEDU51C6 MINGW64 /c/goprojects/src/test
$ go build -gcflags "-m -m -l" test2.go
# command-line-arguments
.\test2.go:6:12: main new(string) does not escape
加上printf函数后,内存逃逸发生,因为printf里面接收interface,类型不明确,所以逃逸:
package main
import "fmt"
func main() {
//使用new关键字,也不一定分配到堆上
//由于在main之外没有使用过str这个变量,所有分配到栈上
str := new(string)
*str = "hello"
fmt.Printf("str :%s, ", str)
}
duke@DUKEDU51C6 MINGW64 /c/goprojects/src/go5期/03-比特币/test
$ go build -gcflags "-m -m -l" test2.go
# command-line-arguments
.\test2.go:11:13: str escapes to heap
.\test2.go:11:13: from ... argument (arg to ...) at .\test2.go:11:12
.\test2.go:11:13: from *(... argument) (indirection) at .\test2.go:11:12
.\test2.go:11:13: from ... argument (passed to call[argument content escapes]) at .\test2.go:11:12
.\test2.go:8:12: new(string) escapes to heap
.\test2.go:8:12: from str (assigned) at .\test2.go:8:6
案例三:泄露参数时不逃逸
package main
type People1 struct {
Name string
Age int
}
//不逃逸
func getInfo1(p *People1) *People1 {
return p
}
func getInfo2(p People1) *People1 {
return &p
}
func main() {
p1 := People1{
Name: "Lily",
Age: 20,
}
_ = getInfo1(&p1) //没逃逸
_ = getInfo2(p1) //逃逸
}
总结:
-
分配到栈上,性能一定比动态分配到堆上好。
-
具体分配到堆还是栈,对于编程人员是不可见的。