1、概述
栈的作用域是函数、当函数执行结束后、栈的内存也会被销毁。堆的作用域通常是跨函数的
当一个变量在函数外部被引用、此时需要把变量转移到堆上,我们一般说发生了逃逸
go语言中当访问一个引用对象时,实际是通过一个指针来间接访问、此时如果再访问里面的引用成员,往往会造成二次访问,这种操作很有可能会导致不必要的逃逸现象、因此我们在使用时,应尽量避免go语言的指针
在 Go 语言中,由于使用了指针而导致变量出现不必要的逃逸,主要是因为编译器无法证明这些指针的生命周期局限于函数内部,因此将其分配到堆上。以下是所有常见的由于不正确使用指针导致不必要逃逸的情况:
2、案例
1. 指针引用局部变量
当指针指向一个局部变量时,Go 编译器会将局部变量分配到堆上,因为指针可能在函数返回后仍然被使用。
func foo() *int {
x := 42 // x 逃逸到堆上,因为返回了指向 x 的指针
return &x
}优化方法:避免返回指针,直接返回值。
2. 将局部变量地址传递给外部函数
如果将局部变量的地址传递给外部函数,编译器无法确定该函数是否会保存该地址,因此会分配到堆上。
func bar(x *int) {
fmt.Println(*x)
}
func foo() {
x := 42 // x 逃逸到堆上,因为其地址传递给了 bar
bar(&x)
}优化方法:如果外部函数不需要持久化变量,传值而非指针。
3. 切片的底层数组逃逸
当指针指向切片的底层数组,且该数组可能会在函数外被修改时,数组可能逃逸。
func foo() []*int {
nums := []int{1, 2, 3}
ptrs := []*int{}
for i := range nums {
ptrs = append(ptrs, &nums[i]) // nums[i] 的地址逃逸
}
return ptrs
}优化方法:避免将切片元素的地址存储到外部。
4. 结构体字段被取地址
当取结构体字段的地址时,字段可能逃逸到堆上,特别是当字段的地址被外部使用时。
type Point struct {
X, Y int
}
func foo() *int {
p := Point{1, 2}
return &p.X // p.X 逃逸,因为返回了其地址
}优化方法:避免直接返回字段地址,优先考虑值传递。
5. 将指针作为接口参数传递
将指针传递给接口参数时,可能导致指针引用的变量逃逸。
func printValue(v interface{}) {
fmt.Println(v)
}
func foo() {
x := 42
printValue(&x) // &x 逃逸,因为接口需要存储指针
}优化方法:使用具体类型或传值而非指针。
6. 在 Goroutine 中使用指针
当局部变量的指针被 Goroutine 捕获时,编译器会将该变量分配到堆上。
func foo() {
x := 42 // x 逃逸到堆上,因为 Goroutine 捕获了 x 的地址
go func() {
fmt.Println(x)
}()
}优化方法:避免在 Goroutine 中直接捕获局部变量,或使用值拷贝。
7. 指针指向数组或切片并在外部使用
数组或切片的地址被取出并传递到外部,导致底层数据逃逸。
func modify(arr *[3]int) {
(*arr)[0] = 42
}
func foo() {
nums := [3]int{1, 2, 3}
modify(&nums) // nums 逃逸到堆上,因为其地址被传递
}优化方法:在局部范围内直接操作数组或切片,避免传递指针。
8. 通过指针间接操作变量
当通过多级指针操作变量时,编译器通常会让变量逃逸到堆上。
func foo() **int {
x := 42 // x 逃逸到堆上,因为通过二级指针返回
px := &x
return &px
}优化方法:简化指针操作,避免多级指针。
9. 隐式指针导致的逃逸
某些情况下,隐式指针的使用也会导致逃逸,例如对字符串和切片的操作。
func appendValue(slice *[]int, value int) {
*slice = append(*slice, value) // slice 底层数组可能逃逸
}
func foo() {
nums := []int{}
appendValue(&nums, 42)
}优化方法:直接返回操作后的切片,避免传递切片指针。
总结
指针导致不必要逃逸的主要原因是 编译器无法证明变量的生命周期局限于函数内部。优化方法包括:
避免不必要的指针传递,优先考虑值传递。
减少对局部变量的地址操作。
避免在 Goroutine 中直接捕获局部变量。
明确变量的作用域,尽可能将其限制在函数内部。
通过遵循这些原则,可以减少不必要的堆分配,从而提升性能。