Golang中变量何时分配在栈上何时分配在堆上?GC处理有区别吗?

Golang中变量何时分配在栈上何时分配在堆上?GC处理有区别吗? 如果一个函数返回结构体,该结构体会在栈上分配。如果返回指向结构体的指针,那么该结构体的分配会在堆上进行吗?

我对此感到困惑,是否有关于新变量分配位置的明确教程?

此外,在垃圾回收方面,栈分配变量和堆分配变量是否存在差异?栈变量(方法调用)是如何进行垃圾回收的?是否存在特殊情况?

5 回复

@JOhn_Stuart,我的回复是否足够解答这个问题?如果已经解决,请将其"标记为已解决"。如需进一步讨论请告知。

2 个赞

更多关于Golang中变量何时分配在栈上何时分配在堆上?GC处理有区别吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


stullis:

我的回答是否足够解决这个问题?如果解决了,请"标记为已解决"。如果需要进一步帮助请告诉我。

你好 @hollowaykeanho, 我该如何将这个问题标记为已解决?

比尔·肯尼迪撰写了一个简短系列文章,让我对此有了清晰的理解。听起来你已经掌握了其中的要点。

据我回忆,基本上有两种情况会发生逃逸到堆的现象:

  • 从函数/方法返回的指针会逃逸到堆(除非包含某些构建标志),因为其内存位置存在于栈的主内存之外
  • 赋值给接口的值也会逃逸到堆,由于接口变量可能变化巨大,无法为其充分分配内存

JOhn_Stuart:

如果函数返回结构体,该结构体会在栈上分配。如果返回指向结构体的指针,该结构体分配会在堆上进行吗?

据我理解,只要涉及指针值(无论在函数内部还是外部),结构体分配总是在堆上进行。

链接:Go语言中结构体的栈与堆分配及其与垃圾回收的关系 - Stack Overflow(建议先阅读PeterSO的反汇编解析,再看Sonia的公认答案)。

实际需要关注的是CALL ,runtime.new+0(SB)这个指令,这里正在调用堆分配机制。

JOhn_Stuart:

对此我感到困惑,是否有关于新变量分配位置的权威教程?

另外,栈分配变量和堆分配变量在垃圾回收方面是否存在差异?栈变量(方法调用)如何进行垃圾回收?是否存在特殊情况?

这份Go 101手册是否足够解答?内存块 - Go 101


我还附上Go反汇编指南以备探索之需:https://www.grant.pizza/dissecting-go-binaries/

在Go语言中,变量分配在栈还是堆上是由编译器的逃逸分析(escape analysis)决定的,而不是简单地由返回类型(值或指针)决定。编译器会分析变量的作用域,如果变量可能在函数返回后仍然被引用(逃逸到堆),则分配在堆上;否则分配在栈上。

逃逸分析示例

package main

// 情况1:结构体可能分配在栈上(未逃逸)
func createStruct() MyStruct {
    return MyStruct{Value: 42} // 可能栈分配
}

// 情况2:结构体可能分配在堆上(逃逸)
func createPointer() *MyStruct {
    return &MyStruct{Value: 42} // 逃逸到堆
}

// 情况3:参数逃逸到堆
func processPointer(s *MyStruct) {
    s.Value = 100
}

type MyStruct struct {
    Value int
}

func main() {
    s1 := createStruct()   // 通常栈分配
    s2 := createPointer()  // 堆分配
    processPointer(s2)     // s2已逃逸到堆
}

要查看编译器的逃逸分析结果,可以使用:

go build -gcflags="-m" main.go

输出会显示类似:

./main.go:8:6: can inline createStruct
./main.go:12:6: can inline createPointer
./main.go:12:16: &MyStruct{...} escapes to heap

垃圾回收差异

  • 栈分配变量:函数返回时自动释放,不参与GC。栈内存管理通过函数调用栈自动处理,效率极高。
  • 堆分配变量:由Go的垃圾回收器(GC)管理。当没有任何引用指向堆对象时,GC会在下次回收周期中释放内存。

特殊情况

  1. 闭包捕获的变量
func closureExample() func() int {
    x := 10  // x逃逸到堆
    return func() int {
        return x
    }
}
  1. 接口类型赋值
var iface interface{} = MyStruct{Value: 42}  // 结构体可能逃逸到堆
  1. 大对象:即使没有显式逃逸,过大的结构体也可能直接分配在堆上。

GC只处理堆内存,栈内存在函数返回时立即回收。这就是为什么堆分配会带来GC开销,而栈分配没有GC成本。在实际编程中,应该信任编译器的逃逸分析优化,而不是手动尝试控制分配位置。

回到顶部