Go的内存逃逸

今天写代码的时候,突然发现变量的数值返回值和希望的不一样。然后发现一个有趣的。嘿嘿嘿。demo如下:

func main() {
	a := demo()
	v := a()
	fmt.Println(v) //1

	v1 := a()
	fmt.Println(v1)

}
func demo() func() int {
	i := 0
	test := func() int {
		i++
		return i
	}
	return test
}

问题就在于fmt.Println(v1)为什么会为2。

先说一下内存逃逸:

内存逃逸:

  • 产生原因:主要就是因为原本应该在栈区的数据,逃逸到了堆区。如下:
    • 在方法内把局部变量指针返回 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
    • 发送指针或带有指针的值到 channel 中。 在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
    • 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
    • slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。 slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
    • 在 interface 类型上调用方法。 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
import "fmt"
type A struct {
 s string
}
// 这是上面提到的 "在方法内把局部变量指针返回" 的情况
func foo(s string) *A {
 a := new(A) 
 a.s = s
 return a //返回局部变量a,在C语言中妥妥野指针,但在go则ok,但a会逃逸到堆
}
func main() {
 a := foo("hello")
 b := a.s + " world"
 c := b + "!"
 fmt.Println(c)
}
  • 避免内存逃逸
    • 有一个函数叫做noescape可以在逃逸分析中隐藏一个指针。

为什么是2呢?

懒得画图了,直接写注释吧
func main() {
	a := demo()    //2:在这里进行调用
	v := a()       //5:v就指向了匿名函数的内存空间
	fmt.Println(v) //1

	v1 := a() //7:v就指向了匿名函数的内存空间,类似于5
	fmt.Println(v1)
}
func demo() func() int { //1:先分配内存地址,如:0X......,但是这个时候,内部的匿名函数还未分配村村地址。
	i := 0               //3:给i开辟内存空间(栈),变量i指向内存空间
	test := func() int { //4:分配内存地址
		i++
		return i //6:指向i的内存地址并改变值
	}
	return test
}

/**
Final:因为这个i是局部变量,按照正常理解就是,随着demo函数的结束从而销毁
但是在函数的返回值是一个函数,并且这个返回函数又再次涉及到了这个i,这个i就不能在随着demo函数的结束而销毁。发生了内存逃逸。
*/

注意:这里的序号不代表运行时的顺序。

3:怎么判断变量是分配在堆还是栈上?

引用官方的话:How do I know whether a variable is allocated on the heap or the stack?
From a correctness standpoint, you don’t need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function’s stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carpe-Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值