当前位置: 首页 > news >正文

Golang中逃逸现象, 变量“何时栈?何时堆?”

目录

什么是栈

什么是堆

栈 vs 堆(核心区别)

GO编译器的逃逸分析

什么是逃逸分析?

怎么看逃逸分析结果?

典型“会逃逸”的场景

闭包捕获局部变量

返回或保存带有“底层存储”的容器

经由接口/反射/fmt 等导致装箱或被长期保存

把指针/引用存入全局、堆对象或长生命周期结构

​​​​​​​参数“内容”被函数保留(编译器推不动)

GO中的“堆/栈”怎么落地

落地

是否上堆由逃逸分析决定

什么时候“应该”用谁?


什么是栈

是每个线程私有、按先进后出管理的调用临时区。

什么是堆

是进程共享、由内存分配器/GC统一管理的动态内存区。

栈 vs 堆(核心区别)

  • 归属:栈是“每个线程一个栈”;堆是“整个进程(多线程)共享一大片内存”

  • 用途:栈放调用过程相关的数据(返回地址、保存寄存器、局部变量等);堆放动态创建、可跨函数/长期存在的数据

  • 生命周期:栈随函数返回自动回收(栈帧弹出);堆由程序(free/delete)或垃圾回收器回收

  • 分配/释放成本:栈是简单的指针移动,极快;堆需要向分配器申请/释放,相对慢

  • 访问局部性:栈一般连续,缓存友好;堆可能碎片化

  • 常见错误:栈——递归太深/局部数组过大导致栈溢出;堆——内存泄漏/重用已释放内存/碎片

  • 大小:栈通常较小且固定/可增长(按语言/平台而定);堆通常大很多(由 OS/运行时管理)

GO编译器的逃逸分析

go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis)当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。 go语言声称这样可以释放程序员关于内存的使用限制,更多的让程序员关注于程序功能逻辑本身。

什么是逃逸分析?

编译器在编译期判断一个变量是否会在当前函数返回后仍被引用(“逃出”当前栈帧)。

  • 不逃逸 → 尽量放在上,分配/回收极快;

  • 逃逸 → 放到上,由运行时/GC 管理,带来分配与 GC 成本。

怎么看逃逸分析结果?

在你的包目录运行(推荐关掉内联更好读):

go build -gcflags='all=-m -l' ./...
# 或者对测试/基准:
go test  -run=^$ -bench=. -gcflags='all=-m -l' ./...

常见输出含义:

  • moved to heap: x / escapes to heap:变量 x 上堆了

  • does not escape:未逃逸(可栈上)

  • leaking param / leaking param content:把参数或其内容泄露到了可能长寿命的位置(导致逃逸)

典型“会逃逸”的场景

返回局部变量的地址/引用

func f1() *int {x := 10return &x // x 逃逸:必须活到函数返回之后
}

闭包捕获局部变量

func f2() func() {x := 0return func() { x++ } // x 被闭包捕获 → 逃逸
}

返回或保存带有“底层存储”的容器

func f3(n int) []int {s := make([]int, n)return s // s 的底层数组需在返回后仍然存活 → 逃逸
}

经由接口/反射/fmt 等导致装箱或被长期保存

func f4(b []byte) {_ = fmt.Sprintf("%x", b) // b 常见会逃逸(fmt 可变参、接口装箱)
}

​​​​​​​把指针/引用存入全局、堆对象或长生命周期结构

var g []*T
func f5(p *T) {g = append(g, p) // p 的“内容”被长期保存 → 逃逸
}

​​​​​​​参数“内容”被函数保留(编译器推不动)

func keepPtr(pp **int) { stash = *pp } // 比如存到包级变量
func caller() {x := 1p := &xkeepPtr(&p) // 报 "leaking param content: p"
}

GO中的“堆/栈”怎么落地

落地

  • 语法上没有显式“栈/堆”关键字;逃逸分析决定变量放栈还是堆

  • 一般规律:不逃逸的局部数据可在栈上;返回到函数外/被闭包捕获等会逃逸到堆

  • make(slice/map/channel)和 new 只是创建方式,是否上堆由逃逸分析决定,堆内存由 GC 回收

是否上堆由逃逸分析决定

type T struct { buf [1024]byte }func f() *T {t := T{}   // 语义上是局部变量;若返回其地址 => 逃逸到堆(由编译器决定)return &t
}func g() {t := T{}   // 不逃逸的话,可能在栈上分配,函数返回就回收_ = t
}

什么时候“应该”用谁?

  • 短生命周期、只在当前调用链内使用:栈(语言通常自动用栈)

  • 需要跨函数/跨协程/长期缓存:堆(动态分配)

  • 在拥有 GC 的语言(Go/Java/Python)中,写法更关注语义,由编译器/运行时决定最终放栈还是堆;只需要留意可能引发逃逸的用法和不必要的大对象分配。


文章转载自:

http://9XmMDiME.kpqjr.cn
http://uEnce5Vy.kpqjr.cn
http://v5b2FBcO.kpqjr.cn
http://EarGqfZE.kpqjr.cn
http://cgtsHlSt.kpqjr.cn
http://ToPuSSIP.kpqjr.cn
http://M5RsX8rd.kpqjr.cn
http://OYtbnIzK.kpqjr.cn
http://PQM7T1dV.kpqjr.cn
http://MYCXprgD.kpqjr.cn
http://ARCyyNHX.kpqjr.cn
http://TshW9khi.kpqjr.cn
http://EfSgyVq8.kpqjr.cn
http://vxjDZVHa.kpqjr.cn
http://xexAM0s7.kpqjr.cn
http://nRasAhks.kpqjr.cn
http://xHer1fX5.kpqjr.cn
http://H9BEfITQ.kpqjr.cn
http://7ugAuoNH.kpqjr.cn
http://kQNRAizp.kpqjr.cn
http://4B1fQSuc.kpqjr.cn
http://lBuxJZ9Z.kpqjr.cn
http://o4de38UR.kpqjr.cn
http://doL1SCif.kpqjr.cn
http://pRVlcQOn.kpqjr.cn
http://sTDNPXjm.kpqjr.cn
http://KLaHdhaN.kpqjr.cn
http://acqwKXES.kpqjr.cn
http://Q7Osd4mL.kpqjr.cn
http://WVB5PZJw.kpqjr.cn
http://www.dtcms.com/a/370869.html

相关文章:

  • 我用Claude Code 开发了一个浏览器插件
  • LRU 算法和 LFU 算法有什么区别?
  • Cursor安装使用 与 Cursor网页端登录成功,客户端怎么也登陆不上
  • vue + ant-design-vue + vuedraggable 实现可视化表单设计器
  • 未来教育行业的 Go 服务开发解决方案与实践
  • 为什么ubuntu大文件拷贝会先快后慢?
  • SQL-窗口函数
  • buuctf-鸡藕椒盐味,[NPUCTF2020]EzRSA,[WUSTCTF2020]大数计算
  • OpsManage 项目启动脚本与 Docker 配置深度分析
  • 智能制造——解读97页汽配行业ERP整体解决方案【附全文阅读】
  • LIO-SAM 算法从入门到部署实践
  • ES6 核心特性详解:从变量声明到函数参数优化
  • 云手机在企业办公中的作用
  • 2025高教社国赛数学建模C题参考论文(含模型和代码)
  • RPC 和 HTTP 的区别
  • 通过Idea 阿里插件快速部署java jar包
  • 在Ubuntu 22.04系统中无需重启设置静态IP地址
  • 数据结构中排序的时间、空间复杂度以及稳定性
  • 面试开发工程师需要做哪些准备
  • hot100-贪心算法(附图解思路)
  • 京东商品属性API数据解析:颜色、尺寸与材质
  • 附051.Kubernetes Karmada kubectl 插件部署联邦及使用
  • 从 Excel 趋势线到机器学习:拆解 AI 背后的核心框架​
  • 嵌入式学习笔记--Linux系统编程阶段--DAY06进程间通信-消息队列
  • 【Beetle RP2350】摇杆控制自定义角度旋转舵机
  • 波特率vs比特率
  • C++ 14新增特性以及代码示例
  • SDRAM详细分析-08 数据手册解读
  • 51单片机---硬件学习(电子琴、主从应答模式、modbus模型、DS18B20传感器显示温度)
  • Blender 3D建模工具学习笔记