在 Go 语言中,局部变量和全局变量的内存分配位置不一样,初始化与否也会影响落在哪一段。这里按类别整理:
程序的内存布局
程序在运行时的内存布局通常包括以下几个部分:
- 代码段(Text Segment):存储程序的可执行指令,是只读的。
- 数据段(Data Segment):存储已初始化的全局变量和静态变量。
- BSS 段(Block Started by Symbol):存储未初始化的全局变量和静态变量。
- 堆(Heap):用于动态分配的内存,通常由程序显式分配和释放。
- 栈(Stack):用于存储函数调用的上下文,包括局部变量、函数参数和返回地址等。
内存布局示意图
程序的内存布局可以用以下示意图表示:
---------------------
| 栈 |
| (局部变量) |
---------------------
| 堆 |
| (动态分配内存) |
---------------------
| BSS |
| (未初始化全局变量) |
---------------------
| 数据段 |
| (已初始化全局变量) |
---------------------
| 代码段 |
| (程序指令) |
---------------------全局变量
全局变量(包级变量)是在包级别声明的变量,它们的作用域为整个包,并且在程序启动时就分配内存。
初始化的全局变量
初始化的全局变量在程序启动时分配内存,并存储在数据段(Data Segment)中。数据段是内存中专门用来存储程序的静态数据(包括全局变量、静态变量等)的区域。
例如:
package main
var initializedGlobalVar = 42
func main() {
// ...
}未初始化的全局变量
未初始化的全局变量在程序启动时也会分配内存,但它们存储在 BSS 段(Block Started by Symbol),BSS 段专门用来存储程序中未显式初始化的全局变量。
例如:
package main
var uninitializedGlobalVar int
func main() {
// ...
}局部变量
局部变量是在函数或代码块内部声明的变量,它们的作用域仅限于声明它们的函数或代码块。
初始化的局部变量
初始化的局部变量在函数或代码块执行时分配内存,并存储在栈(Stack)中。栈内存用于存储函数调用过程中的临时数据,包括局部变量、函数参数等。栈内存具有快速分配和释放的特点。
例如:
func exampleFunction() {
initializedLocalVar := 42
_ = initializedLocalVar // 使用一下,避免编译报错
}未初始化的局部变量
未初始化的局部变量在函数或代码块执行时也会分配内存,并存储在栈中。Go 语言会自动为这些变量赋予零值。
例如:
func exampleFunction() {
var uninitializedLocalVar int
_ = uninitializedLocalVar // 使用一下,避免编译报错
}代码示例
四类变量放在一起看:
package main
import "fmt"
// 初始化的全局变量
var initializedGlobalVar = 42
// 未初始化的全局变量
var uninitializedGlobalVar int
func main() {
// 初始化的局部变量
initializedLocalVar := 10
// 未初始化的局部变量
var uninitializedLocalVar int
fmt.Println("Initialized Global Variable:", initializedGlobalVar)
fmt.Println("Uninitialized Global Variable:", uninitializedGlobalVar)
fmt.Println("Initialized Local Variable:", initializedLocalVar)
fmt.Println("Uninitialized Local Variable:", uninitializedLocalVar)
}代码段(Code Segment)
代码段是内存中的一个区域,用于存储程序的机器码,即程序的可执行指令。代码段是只读的,以防止程序在运行时修改其自身的指令。全局变量和局部变量并不存储在代码段中,而是存储在数据段、BSS 段或栈中。
逃逸分析
局部变量默认在栈上,但编译器会做逃逸分析(escape analysis):变量的地址被带出函数、被闭包捕获、或者体积过大时,它会被搬到堆上,生命周期改由 GC 管。
package main
import "fmt"
func main() {
fmt.Println(foo())
}
func foo() *int {
a := 42
return &a // a 的地址逃出函数,a 被分配到堆上
}判断某个变量到底落在栈还是堆,用 go build -gcflags=-m 看编译器的分析输出。
总结
- 全局变量:在程序启动时分配内存,已初始化的存储在数据段,未初始化的存储在 BSS 段。
- 局部变量:在函数或代码块执行时分配内存,存储在栈中,不论是否初始化,未初始化的局部变量会被赋予零值。
- 代码段:存储程序的机器码,是只读的。
变量声明位置和初始化与否,决定它落在数据段、BSS 段还是栈上;函数体内的变量跟着栈走,进出函数就分配回收,这也是局部变量生命周期短的原因。