在谈论变量的生命周期之前,先了解一下两个重要的概念:栈(stack)堆(heap)

栈(stack)

1.概念

栈是一种拥有特殊规则线性表数据结构,栈只允许往线性表的一端放入数据,之后再这一端取出数据,按照后进先出(请牢记)(LIFO,Last In First Out)的规则,如下图所示:
栈的操作及扩展
上图就是栈的操作及扩展,往栈里面放入元素的操作叫做入栈(push),有的书籍叫做进栈或者压栈,入栈会增加栈里面的元素数量,最后放入的元素总是会在栈的顶部,最先放入的元素总是在栈的底部。
取出元素的时候,只能从栈的顶部开始取,这一操作叫做出栈(pop),取出元素之后,栈的元素数量会减少,最先放入的元素总是最后才会取出。

2.变量和栈的关系

栈可以用于内存分配,熟悉java的同学肯定知道,在java中栈一般用来存放地址,栈的分配和回收的速度非常快。
实例:

1
2
3
4
5
6
7
func calc (a, b int) int {
var c int
c = a * b
var x int
x = c * 10
return x
}

代码说明如下:

  • 第 1 行,传入 a、b 两个整型参数。
  • 第 2 行,声明 c 整型变量,运行时,c 会分配一段内存用以存储 c 的数值。
  • 第 3 行,将 a 和 b 相乘后赋予 c。
  • 第 5 行,声明 x 整型变量,x 也会被分配一段内存。
  • 第 6 行,让 c 乘以 10 后存储到 x 变量中。
  • 第 8 行,返回 x 的值。

堆(heap)

堆在内存分配中类似于在一个房间中摆放各种家具,家具的尺寸有大有小,分配内存的时候,需要找一块能够装下家具的空间在摆放家具,经过反复摆放和腾空家具后,房间里的空间会变得乱七八糟,此时再往空间摆放家具会存在虽然有足够的空间,但是各个空间分布在不同的区域,无法有一段连续的空间来摆放家具的问题,此时,内存分配器就需要对这些碎片化的空间进行优化,如下图所示:
堆的分配及空间
堆分配内存和栈分配内存相比,堆适合不可预知的大小的内存分配,比如Python的list,set等(当然,Python的机制不是使用heap实现的),但是为此付出的代价就是分配的速度较慢,而且会形成内存碎片。

变量逃逸(Escape Analysis)

自动决定变量分配方法,提高运行速率

堆和栈各有优缺点,该怎么在编程中处理这个问题呢?在 C/C++ 语言中,需要开发者自己学习如何进行内存分配,选用怎样的内存分配方式来适应不同的算法需求。比如,函数局部变量尽量使用栈;全局变量、结构体成员使用堆分配等。程序员不得不花费很多年的时间在不同的项目中学习、记忆这些概念并加以实践和使用。

Go 语言将这个过程整合到编译器中,命名为“变量逃逸分析”。这个技术由编译器分析代码的特征和代码生命期,决定应该如何堆还是栈进行内存分配,即使程序员使用 Go 语言完成了整个工程后也不会感受到这个过程。