图解Go语言逃逸

示例:

go 复制代码
func main() {
    main_val := foo(666)
    println(*main_val)
}

func foo(arg_val int) *int {
    var foo_val int = 11
    return &foo_val
}

执行结果:

外部函数使用了子函数的局部变量.理论上来讲.子函数的foo_val的生命周期早就销毁了才对.所以在外层函数main函数中.如果访问子函数的foo_val局部变量.则一定是访问一个已经操作系统回收的空间.

逃逸分析过程示例:

Go语言在编译会自动决定把一个变量放在栈还是放在堆.编译器会做逃逸分析.当发现变量的作用域没有超出函数范围时.就可以放在栈上.反之则必须分配在堆.

示例:

go 复制代码
func main() {
    main_val := foo(666)
    println(*main_val, main_val)
}

func foo(arg_val int) *int {
    var foo_val1 int = 11
    var foo_val2 int = 12
    var foo_val3 int = 13
    var foo_val4 int = 14
    var foo_val5 int = 15
    for i := 0; i < 5; i++ {
       println(&arg_val, &foo_val1, &foo_val2, &foo_val3, &foo_val4, &foo_val5)
    }
    return &foo_val3
}

执行结果:

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

在编译的时候foo_val3被编译器判定为逃逸变量.因此将foo_val3放到堆中开辟.

new的变量在栈还是堆:

示例:

go 复制代码
func main() {
    main_val := foo(666)
    println(*main_val, main_val)
}

func foo(arg_val int) *int {
    var foo_val1 *int = new(int)
    var foo_val2 *int = new(int)
    var foo_val3 *int = new(int)
    var foo_val4 *int = new(int)
    var foo_val5 *int = new(int)
    for i := 0; i < 5; i++ {
       println(&arg_val, &foo_val1, &foo_val2, &foo_val3, &foo_val4, &foo_val5)
    }
    return foo_val3
}

执行结果:

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

流程图:

普遍的逃逸规则:

逃逸的普遍规则就是如果变量需要使用堆空间.就应该进行逃逸.实际上Go语言并不仅把逃逸的规则定的如此泛泛.Go语言中有许多场景具备出现逃逸的现象.

一般在给一个引用类对象中的引用类成员进行赋值时可能出现逃逸现象.可以理解为.访问一个引用对象实际上是底层通过一个指针来间接的访问.但如果访问里面的引用成员.则会有第二次间接访问.这样操作这部分对象时极大可能会出现逃逸的现象.

Go语言中有引用类型func(函数类型) interface(接口类型) slice(切片类型) map(字典类型) channel(管道类型) 和*(指针类型)等.

逃逸范例1:

go 复制代码
func main() {
    data := []interface{}{100, 200}
    data[0] = 100
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知data0=100发生了逃逸.

逃逸范例2:

如果变量是mapstringinterface{}类型且尝试通过赋值.则必定出现逃逸.

go 复制代码
func main() {
    data := make(map[string]interface{})
    data["key"] = 200
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过得知.data"key"=200发生了逃逸.

逃逸范例3:

如果mapinterface{}interface{}类型尝试通过赋值,则会导致key和value的赋值出现逃逸.

go 复制代码
func main() {
    data := make(map[interface{}]interface{})
    data[100] = 200
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知data100=200中.100和200均发生了逃逸.

逃逸范例4:

如果变量是mapstring\[\]string数据类型.则赋值会发生\[\]string逃逸.

go 复制代码
func main() {
    data := make(map[string][]string)
    data["key"] = []string{"value"}
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知\[\]string{...}切片发生了逃逸.

逃逸范例5:

如果变量是\[\]*int数据类型.则赋值的右值会发生逃逸现象.

go 复制代码
func main() {
    a := 10

    data := []*int{nil}
    data[0] = &a
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果可知.moved to heap:a 最终将变量a移动到了堆上.

如果变量是func(* int)函数类型.则进行函数赋值.会使传递的形参出现逃逸现象.

go 复制代码
func main() {
    data := 10
    f := foo
    f(&data)
    fmt.Println(data)
}

func foo(a *int) {
    return
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知data已经被逃逸到堆上.

逃逸范例7:

如果变量是func(\[\]string)函数类型.则进行\[\]string{"value"}赋值时会使传递的参数出现逃逸现象.

go 复制代码
func main() {
    s := []string{"abeld"}
    foo(s)
    fmt.Println(s)
}

func foo(a []string) {
    return
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知s escapes to heap.s被逃逸到堆上.

逃逸范例8:

go 复制代码
func main() {
    ch := make(chan []string)

    s := []string{"aceld"}
    go func() {
       ch <- s
    }()
}

用命令go build -gcflags="-m" main.go执行可以倒如下结果.

通过结果得知\[\]string{...} escapes to heap,s被逃逸到堆上.

语雀地址www.yuque.com/itbosunmian...?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

悠悠的往事.回流的水.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

相关推荐
JustHappy12 分钟前
古法编程秘籍(二):什么是代码模块化?别背概念,把房间收拾明白就够了
前端·后端
小江的记录本19 分钟前
【JVM虚拟机】堆内存分代模型:年轻代(Eden+Survivor)、老年代、元空间Metaspace(附《思维导图》+《面试高频考点清单》)
java·前端·jvm·后端·python·spring·面试
IT_陈寒4 小时前
Python闭包里藏的这个坑,差点让我加班到凌晨
前端·人工智能·后端
IT_陈寒4 小时前
Java注解空指针?这个坑我踩得莫名其妙
前端·人工智能·后端
土狗TuGou5 小时前
SQL内功笔记 · 第8篇:事务的四大特性与隔离级别
数据库·笔记·后端·sql·mysql·oracle
ZengLiangYi5 小时前
React Query + REST API 最佳实践
javascript·后端·react.js
星浩AI5 小时前
项目实战:合同智能审批 · LangGraph + HITL 人机协同方案 [有源码]
后端·langchain·agent
JavaGuide5 小时前
Codex 接入第三方模型 DeepSeek、GLM、Kimi 教程:CC-Switch 和 Codex++ 两种方案对比
后端·ai编程
ZengLiangYi5 小时前
Fastify 加 Electron:把 Web 服务嵌进桌面应用
前端·javascript·后端
李白你好5 小时前
页面资产梳理 · 技术指纹识别 · Spring 端点探测
java·后端·spring