说说 Go 中闭包的底层原理?

1. 什么是闭包?

一个函数内引用了外部的局部变量,这种现象,就称之为闭包。

例如下面的这段代码中,adder 函数返回了一个匿名函数,而该匿名函数中引用了 adder 函数中的局部变量 sum ,那这个函数就是一个闭包。

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

而这个闭包中引用的外部局部变量并不会随着 adder 函数的返回而被从栈上销毁。

我们尝试着调用这个函数,发现每一次调用,sum 的值都会保留在 闭包函数中以待使用。

func main() {
     valueFunc:= adder()
     fmt.Println(valueFunc(2))     // output: 2
     fmt.Println(valueFunc(2))   // output: 4
}

闭包的底层原理

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    valueFunc:= adder()
    fmt.Println(valueFunc(2))     // output: 2
}

我们先对它进行逃逸分析,很容易发现 sum 作为 adder 函数局部变量,并不是分配在栈上,而是分配在堆上的。

这就解决了第一个疑惑:为什么 adder 函数返回后, sum 不会随之销毁?

$ go build -gcflags="-m -m -l" demo.go
# command-line-arguments
./demo.go:8:3: adder.func1 capturing by ref: sum (addr=true assign=true width=8)
./demo.go:7:9: func literal escapes to heap:
./demo.go:7:9:   flow: ~r0 = &{storage for func literal}:
./demo.go:7:9:     from func literal (spill) at ./demo.go:7:9
./demo.go:7:9:     from return func literal (return) at ./demo.go:7:2
./demo.go:6:2: sum escapes to heap:
./demo.go:6:2:   flow: {storage for func literal} = &sum:
./demo.go:6:2:     from func literal (captured by a closure) at ./demo.go:7:9
./demo.go:6:2:     from sum (reference) at ./demo.go:8:3
./demo.go:6:2: moved to heap: sum
./demo.go:7:9: func literal escapes to heap
./demo.go:15:23: valueFunc(2) escapes to heap:
./demo.go:15:23:   flow: {storage for ... argument} = &{storage for valueFunc(2)}:
./demo.go:15:23:     from valueFunc(2) (spill) at ./demo.go:15:23
./demo.go:15:23:   flow: {heap} = {storage for ... argument}:
./demo.go:15:23:     from ... argument (spill) at ./demo.go:15:13
./demo.go:15:23:     from fmt.Println(valueFunc(2)) (call parameter) at ./demo.go:15:13
./demo.go:15:13: ... argument does not escape
./demo.go:15:23: valueFunc(2) escapes to heap

可另一个问题,又浮现出来了,就算它不会销毁,那闭包函数若是存储的若是 sum 拷贝后的值,那每次调用闭包函数,里面的 sum 应该都是一样的,调用两次都应该返回 2,而不是可以累加记录。

因此,可以大胆猜测,闭包函数的结构体里存储的是 sum 的指针。

为了验证这一猜想,只能上汇编了。

通过执行下面的命令,可以输出对应的汇编代码

go build -gcflags="-S" demo.go
```输出的内容相当之多,我提取出下面最关键的一行代码,它定义了闭包函数的结构体。

其中 F 是函数的指针,但这不是重点,重点是 sum 存储的确实是指针

type.noalg.struct { F uintptr; "".sum *int }(SB), CX

posted @ 2025-06-05 09:40  Zero&&One  阅读(22)  评论(0)    收藏  举报