【Go】堆和栈变量
点击阅读更多查看文章内容
在 Go 语言中,变量的分配位置(堆或栈)是由 编译器 根据变量的生命周期和使用方式自动决定的。以下是堆栈分配的基本规则和常见情况:
1. 栈分配的变量
栈分配的变量通常具有 确定且较短的生命周期,例如局部变量。栈分配速度快,但空间有限。
栈分配的典型情况:
局部变量:
在函数内部声明的变量,且未逃逸到函数外部。
示例:
1
2
3
4func foo() {
x := 10 // x 分配在栈上
fmt.Println(x)
}
函数参数:
传递给函数的参数变量。
示例:
1
2
3func bar(y int) {
fmt.Println(y) // y 分配在栈上
}
2. 堆分配的变量
堆分配的变量通常具有 不确定或较长的生命周期,例如逃逸到函数外部的变量或动态分配的内存。堆分配速度较慢,但空间较大。
堆分配的典型情况:
全局变量
动态分配的内存:使用
new或make分配的内存通常分配到堆上。逃逸到函数外部的变量:
如果变量的地址被返回或传递到函数外部,编译器会将其分配到堆上。
示例:
1
2
3
4func escape() *int {
x := 10 // x 逃逸到堆上
return &x
}
大对象:
较大的对象(如大数组或结构体)可能会分配到堆上,以避免栈溢出。
示例:
1
2
3
4func large() {
var arr [100000]int // arr 可能分配到堆上
fmt.Println(arr[0])
}
闭包捕获的变量:
被闭包捕获的变量会分配到堆上,因为闭包可能在函数返回后继续使用这些变量。
示例:
1
2
3
4
5
6func closure() func() int {
x := 10 // x 逃逸到堆上
return func() int {
return x
}
}
3. 逃逸分析(Escape Analysis)
Go 编译器通过 逃逸分析 决定变量是否分配到堆上。逃逸分析的目标是尽可能将变量分配到栈上,以提高性能。
- 栈分配:如果变量的生命周期仅限于函数内部,编译器会将其分配在栈上。栈分配速度快,且不需要垃圾回收。
- 堆分配:如果变量的生命周期超出了函数范围(例如返回给调用者或存储到全局变量中),编译器会将其分配在堆上。堆分配需要垃圾回收器管理。
逃逸的常见场景
- 函数内的变量被函数外使用:
- 变量的地址被传递到函数外部(函数返回变量指针)
- 函数内的变量赋值给全局变量
- 函数内的变量传递给另一个函数
- 变量被闭包捕获
- 变量大小超出栈的范围:
- 较大的变量可能分配到堆上,以避免栈溢出。
- 变量的生命周期:
- 生命周期较长的变量可能分配到堆上。
4. 堆栈分配的总结
| 特性 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 分配方式 | 编译器自动分配/释放(LIFO) | 手动分配(如 new/make)或 GC 管理 |
| 生命周期 | 随函数调用结束自动销毁 | 需显式释放(或由 GC 回收) |
| 访问速度 | 极快(CPU 缓存友好) | 较慢(可能触发缺页中断) |
| 内存碎片 | 无(严格顺序分配) | 可能有(动态分配导致) |
| 线程安全 | 每个线程独享栈 | 全局共享,需同步机制 |
| 典型存储内容 | 局部变量、函数参数、返回值 | 动态分配的对象(如 struct、slice)、逃逸对象 |
栈:每个 Goroutine 创建时会被分配一个 独立的栈(默认大小 2KB,随需动态增长,最大可达 1GB)。
堆:所有 Goroutine 共享堆:通过 new、make 或逃逸分析分配到堆的对象,可被多个 Goroutine 访问。

