变量的生命周期
生命周期
指变量在程序执行过程中存在的时间段。包级别变量的生命周期是整个程序的执行时间。相反,局部变量有一个动态的生命周期:每次执行声明语句时创建一个新的实体,变量一直生存到它变得不可访问,此时它占用的存储空间被回收。函数的参数和返回值也是局部变量,它们在其闭包函数被调用的时候创建。
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程序设计语言》