关于变量分配在堆和栈的问题一直不太了解,直到看了附录几篇文章,有了一个初步的认识。
先看官网怎么说
go变量分配到堆还是栈
从正确的角度来看,你不需要知道。Go中的每个变量只要有引用就会存在。实现选择的存储位置与语言的语义无关。
stack framegarbage-collected heap
在当前的编译器中,如果一个变量的地址被占用,那么该变量就成为堆中的一个候选对象。但是,一些基本的逃逸分析可以识别出某些情况下,这些变量不会在函数的返回后还存在,就可以驻留在栈中。
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import (
"fmt"
"unsafe"
)
func main() {
a := []int{}
a = append(a, 7, 8, 9)
fmt.Printf("len: %d cap:%d data:%+v \n", len(a), cap(a), a)
println(&a, 1)
ap(a)
//apRef(&a)
fmt.Printf("len: %d cap:%d data:%+v \n", len(a), cap(a), a)
println(&a, 2)
p := unsafe.Pointer(&a[2])
q := uintptr(p) + 4
t := (*int)(unsafe.Pointer(q))
fmt.Println(*t)
}
func ap(a []int) {
a = append(a, 10)
println(&a, 3)
}
func apRef(a *[]int) {
*a = append(*a, 10)
println(a, 3, cap(*a))
}
apapRef
- Go函数是值传递
ap 去操作 slice a 的值时,在 ap 复制一份a - Go编译器将常在栈帧中分配本地变量 (高效存储,无需回收下次直接覆盖)
ap 对内部复制的变量 a 存在自己的 frame stack, 返回后该frame 中的内存状态不确定,因为go可能会在分配给别的函数 frame时覆盖它 - Go编译器无法证明变量在函数返回后不会被引用 则必须存在堆上(有垃圾回收)
apRef 参数是指针,操作时,不确定返回后指针是否会被引用,故创建变量到堆上,由指针a指向堆值,main后续访问a值时,是访问的堆上的值
因而ap对a的修改返回后在main里没有体现,停留在未覆盖前的ap frame stack 里;
相反 apRef 对 a指针对应的数据修改保存到堆上,只要有引用就不会被回收,在main中仍可访问
原文还提到一个slice growth的问题,有兴趣可以看下
当然学会怎么分析是最重要的,下边提到的方法要多练习练习
内存分析
go build -gcflags "-m -m"go tool compile -helpgo test -run none -bench AlgorithmOne -benchtime 3s -benchmemgo test -run none -bench AlgorithmOne -benchtime 3s -benchmem -memprofile mem.out
附录