变量的生命周期
生命周期指变量在程序执行过程中存在的时间段。包级别变量的生命周期是整个程序的执行时间。相反,局部变量有一个动态的生命周期:每次执行声明语句时创建一个新的实体,变量一直生存到它变得不可访问,此时它占用的存储空间被回收。函数的参数和返回值也是局部变量,它们在其闭包函数被调用的时候创建。
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
}
在如上代码片段中,变量t在每次for循环的开始创建,变量x和y在循环的每次迭代中创建。
那么垃圾回收器如何知道一个变量是否应该被回收?基本思路是每个包级别的变量,以及每一个当前执行函数的局部变量,可以作为追溯该变量的路径的源头,通过指针和其他方式的引用可以找到变量。如果变量的路径不存在,那么变量变得不可访问,因此它不会影响任何其他的计算过程。
因为变量的生命周期是通过它是否可达来确定的,所以局部变量可在包含它的循环的一次迭代之外继续存活。即使包含它的循环已经返回,它的存在还可能延续。
编译器可以选择使用堆或栈上的空间来分配,且这个选择不是基于使用var或new关键字来声明变量:
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
这里,x一定使用堆空间,因为它在f函数返回以后还可以从变量global访问,尽管它被声明为一个局部变量。这种情况我们说x从f中逃逸。相反,当g函数返回时,变量*y变得不可访问,可回收。因此*y没有从g中逃逸,所以编译器可以安全地在栈上分配*y,即便使用new函数来创建它。
任何情况下,逃逸的概念使开发者不需要额外费心来写正确的代码,但要记住它在性能优化的时候是有好处的,因为每一次变量逃逸都需要一次额外的内存分配过程。
垃圾回收对于写出正确的程序有巨大的帮助,但是免不了考虑内存的负担。不需要显式分配和释放内存,但是变量的生命周期是写出高效程序所必需清楚的。例如,在长生命周期对象中保持短生命周期对象不必要的指针,特别是在全局变量中,会阻止垃圾回收器回收短生命周期的对象空间。
参考资料
-
《Go程序设计语言》
2363

被折叠的 条评论
为什么被折叠?



