Go语言里面的堆跟栈 + new 和 make + 内存逃逸 + 闭包
在 Go 语言中,堆(Heap)和栈(Stack)是内存管理中的两个重要概念,它们在内存分配、数据存储和使用场景等方面存在明显差异。
栈(Stack)
栈是一种具有后进先出(LIFO)特性的数据结构,在程序运行时,系统会为每个线程分配一个栈空间,用于存储函数调用过程中的局部变量、函数参数(形参)、返回地址等(函数调用的上下文)信息。
工作原理
- 入栈(Push):当一个函数被调用时,会在栈上为该函数的局部变量和参数分配内存空间,这个过程称为入栈操作。
- 出栈(Pop):当函数执行完毕返回时,系统会自动释放该函数在栈上分配的内存空间,这个过程称为出栈操作。
package mainfunc add(a, b int) int {// 局部变量sum存储在栈上sum := a + breturn sum }func main() {x := 1y := 2// 调用add函数时,参数a、b和局部变量sum会在栈上分配内存result := add(x, y)println(result) }注意:形参a、b是在栈上。
-
特点
- 自动分配和释放:栈上的内存分配和释放是由系统自动完成的,函数调用结束后,栈上的内存可以立即回收。不需要程序员手动干预,因此栈的操作效率较高。
- 内存空间连续:栈上的内存空间是连续的,这使得栈的访问速度非常快。
- 大小有限:每个线程的栈空间大小是有限的,如果函数调用层次过深或者局部变量占用的内存过大,可能会导致栈溢出错误。
知识点:
形式参数(简称形参):定义函数时,函数名后面括号中的变量名。由于它不是实际存在变量,所以又称虚拟变量。
例如:当你定义函数void add(int a, int b)的时候,这里的a和b就是形参。
实际参数(简称实参):调用函数时,函数名后面括号中的表达式。是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等。
例如:当你调用函数add(1 ,2)的时候,这里的1和2就是实参。
堆(Heap)
堆是一块用于动态内存分配的内存区域,程序在运行时可以根据需要在堆上分配和释放内存,通常用于存储生命周期不确定的对象。在 Go 语言中,使用new和make关键字或者直接创建引用类型(如切片、映射、通道等)时,会在堆上分配内存。
堆内存的管理相对复杂,需要垃圾回收(GC)来进行清理,回收速度不如栈快。
工作原理
- 内存分配:当程序需要在堆上分配内存时,会向操作系统请求一块合适大小的内存空间。
- 垃圾回收:当程序不再使用堆上的某个内存块时,Go 语言的垃圾回收器(GC)会自动回收该内存块,以便后续使用。
package mainimport "fmt"func main() {// 使用new关键字在堆上分配内存ptr := new(int)*ptr = 10fmt.Println(*ptr)// 创建切片,切片的底层数组在堆上分配内存slice := make([]int, 3)slice[0] = 1slice[1] = 2slice[2] = 3fmt.Println(slice) }特点
- 动态分配和释放:堆上的内存分配和释放是动态的,需要程序员手动控制或者由垃圾回收器自动处理。
- 内存空间不连续:堆上的内存空间是不连续的,这使得堆的访问速度相对较慢。
总结
栈主要用于存储函数调用的上下文信息,具有自动分配和释放、内存空间连续、操作效率高但大小有限的特点;堆主要用于动态内存分配,具有动态分配和释放、内存空间不连续、大小灵活但访问速度相对较慢的特点。在 Go 语言中,编译器会根据变量的类型和使用场景自动决定将变量分配到栈上还是堆上。
补充1:内存逃逸
内存逃逸,就是程序在执行过程中,某些本应在栈上分配的变量,被“逃逸”到了堆上。
原因:简单来说,就是 Go 的编译器在优化内存分配时,无法确定一个变量的生命周期和作用范围,导致它在堆上分配内存,而不是栈上。
变量逃逸的常见原因:
1、闭包(变量的生命周期超出函数作用域):闭包是导致内存逃逸的一个典型场景。因为闭包会持有外部函数的引用,所以即使外部函数已经返回,闭包内部的变量依然可能在堆上存活。
func main() {f := func() int {x := 42 // 这个变量 x 会逃逸到堆上return x}fmt.Println(f())
}
这里,x 是一个局部变量,但因为 f 是一个闭包,Go 编译器不能确定 x 的生命周期是否结束,所以 x 被分配到了堆上。
2、指针传递:如果你将一个局部变量的指针传递给了其他函数,Go 编译器会推测这个变量的生命周期已经超出了当前作用域,从而将它分配到堆上。
func foo(ptr *int) {fmt.Println(*ptr)
}func main() {x := 42foo(&x) // 这里传递了 x 的地址,x 会被分配到堆上
}
在这个例子中,x 被传递给了 foo 函数,而 foo 是通过指针来访问 x 的。因为 Go 编译器无法确定 x 在 main 函数外部是否会继续使用,所以 x 被分配到了堆上。
3、变量大小不确定(数组或切片的引用):切片类型在传递时会导致数据逃逸。因为切片实际上是对数组的一层封装,如果你在函数外部返回了切片,就可能导致底层的数组逃逸。
func createSlice() []int {arr := [3]int{1, 2, 3} // arr 会逃逸到堆上return arr[:]
}func main() {slice := createSlice()fmt.Println(slice)
}
在这个示例中,使用 make 函数创建的切片 arr 的大小在运行时才能确定,因此 arr会逃逸到堆上。
4、函数返回局部变量指针
package mainfunc escape() *int {x := 10return &x
}func main() {result := escape()println(*result)
}
在这个示例中,escape 函数返回了局部变量 x 的指针,因此 x 会逃逸到堆上。
闭包:闭包是一个函数,它可以访问并操作其外部作用域中的变量,即使该外部作用域已经执行完毕,这些变量也不会被销毁,如下图,匿名函数且是闭包:
// 这个例子中,outerFunction 返回的匿名函数引用了外部函数的局部变量 count, // 形成了闭包。每次调用闭包时,count 的值会持续增加 func outerFunction() func() int {count := 0// 定义一个匿名函数,形成闭包return func() int {count++return count} }func main() {counter := outerFunction()fmt.Println(counter()) // 输出: 1fmt.Println(counter()) // 输出: 2 }匿名函数:强调的是函数没有名称这一特性,常用于实现一些简单的、一次性的功能,比如作为回调函数传递给其他函数,或者在一些临时的逻辑处理中使用。
// 下面的匿名函数没有引用任何外部作用域的变量,所以它只是一个普通的匿名函数,不是闭包。 func main() {// 定义一个匿名函数并立即执行func() {fmt.Println("This is an anonymous function without closure.")}() }具名函数作为闭包
// 下面,inner 是一个具名函数,它引用了外部函数的局部变量 message,形成了闭包 func outer() func() {message := "Hello, Closure!"// 定义一个具名函数作为闭包func inner() {fmt.Println(message)}return inner }func main() {closure := outer()closure() // 输出: Hello, Closure! }匿名函数侧重于函数没有名称,而闭包侧重于函数对外部变量的引用和持有。匿名函数可以是闭包,也可以不是闭包;闭包可以是匿名函数,也可以是具名函数。理
补充2:new 与 make 的使用区别:
在 Go 语言里,new 和 make 都用于内存分配,但它们的用途和使用场景存在明显差异
new 函数:
new 是一个内置函数,其作用是为类型分配一片零值内存空间(只接收一个参数),并且返回指向该接收参数内存空间的指针。
func new(Type) *Type
这里的 Type 代表任意类型,new 函数会返回一个指向该类型零值的指针。
// 使用 new 函数为 int 类型分配内存numPtr := new(int)// 此时 numPtr 指向的 int 类型变量的值为 0(int 类型的零值)fmt.Println(*numPtr) // 修改 numPtr 指向的变量的值*numPtr = 10fmt.Println(*numPtr) // 使用 new 函数为结构体类型分配内存type Person struct {Name stringAge int}personPtr := new(Person)// 结构体的字段被初始化为零值fmt.Printf("Name: %s, Age: %d\n", personPtr.Name, personPtr.Age)
代码解释
- 当调用
new(int)时,会为int类型分配一块内存空间,初始值为0,并返回指向该内存空间的指针numPtr。 - 对于结构体类型
Person,调用new(Person)会为该结构体分配内存,结构体的字段会被初始化为各自类型的零值,然后可以通过指针修改这些字段的值。
make 函数
make 也是一个内置函数,不过它只能用于创建并初始化 slice(切片)、map(映射)和 channel(通道)这三种引用类型。
-
func make(t Type, size ...IntegerType) Type这里的
t必须是slice、map或channel类型,size是可选参数,用于指定初始大小
package mainimport "fmt"func main() {// 创建一个长度为 3,容量为 5 的整数切片slice := make([]int, 3, 5)fmt.Println("切片长度:", len(slice)) fmt.Println("切片容量:", cap(slice)) fmt.Println(slice) // 修改切片元素的值slice[0] = 1slice[1] = 2slice[2] = 3fmt.Println(slice) // 创建一个字符串到整数的映射m := make(map[string]int)// 向映射中添加键值对m["apple"] = 1m["banana"] = 2fmt.Println(m) // 创建一个无缓冲的整数通道ch := make(chan int)go func() {// 向通道发送数据ch <- 10}()// 从通道接收数据num := <-chfmt.Println(num) // 创建一个有缓冲的整数通道,缓冲区大小为 3ch2 := make(chan int, 3)
}
总结
new:主要用于为任意类型分配零值内存并返回指针,通常用于需要显式管理指针的场景。make:专门用于创建并初始化slice、map和channel这三种引用类型,会完成必要的初始化操作,不能用于其他类型。
相关文章:
Go语言里面的堆跟栈 + new 和 make + 内存逃逸 + 闭包
在 Go 语言中,堆(Heap)和栈(Stack)是内存管理中的两个重要概念,它们在内存分配、数据存储和使用场景等方面存在明显差异。 栈(Stack) 栈是一种具有后进先出(LIFO&#…...
蓝桥备赛(11)- 数据结构、算法与STL
一、数据结构 1.1 什么是数据结构? 在计算机科学中,数据结构是一种 数据组织、管理和存储的格式。它是相互之间存在一种 或多种特定关系的数据元素的集合。 ---> 通俗点,数据结构就是数据的组织形式 , 研究数据是用什么方…...
react 19版中路由react-router-dom v7版的使用
路由的安装: npm install react-router-dom在src目录下建一个router文件夹 在router文件夹里面建一个index.tsx index.tsx内容: import React from react; import {BrowserRouter as Router,Routes,Route,Link } from react-router-dom; import ManuLi…...
WPS工具栏添加Mathtype加载项
问题描述: 分别安装好WPS和MathType之后,WPS工具栏没直接显示MathType工具,或者是前期使用正常,由于WPS更新之后MathType工具消失,如下图 解决办法 将文件“MathType Commands 2016.dotm”和“MathPage.wll”从Matht…...
Tauri+React+Ant Design跨平台开发环境搭建指南
TauriReactAnt Design跨平台开发环境搭建指南 一、环境配置与工具链搭建 1.1 基础环境准备 必备组件: Rust工具链(v1.77): curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh Node.js LTS(v20.11.1&a…...
PDF转JPG(并去除多余的白边)
首先,手动下载一个软件(poppler for Windows),下载地址:https://github.com/oschwartz10612/poppler-windows/releases/tag/v24.08.0-0 否则会出现以下错误: PDFInfoNotInstalledError: Unable to get pag…...
std::string的模拟实现
目录 string的构造函数 无参数的构造函数 根据字符串初始化 用n个ch字符初始化 用一个字符串的前n个初始化 拷贝构造 用另一个string对象的pos位置向后len的长度初始化 [ ]解引用重载 迭代器的实现 非const版本 const版本 扩容reserve和resize reserve resize p…...
wordpress自定the_category的输出结构
通过WordPress的过滤器the_category来自定义输出内容。方法很简单,但是很实用。以下是一个示例代码: function custom_the_category($thelist, $separator , $parents ) {// 获取当前文章的所有分类$categories get_the_category();if (empty($categ…...
doris: Oracle
Apache Doris JDBC Catalog 支持通过标准 JDBC 接口连接 Oracle 数据库。本文档介绍如何配置 Oracle 数据库连接。 使用须知 要连接到 Oracle 数据库,您需要 Oracle 19c, 18c, 12c, 11g 或 10g。 Oracle 数据库的 JDBC 驱动程序,您可以从 Maven 仓库…...
mysql中什么机制保证宕机数据恢复
MySQL 通过多种机制来保证在宕机或意外崩溃时数据的完整性和可恢复性。这些机制主要包括 事务日志、崩溃恢复 和 数据持久化 等。以下是 MySQL 中保证数据恢复的核心机制: 1. 事务日志(Transaction Log) 事务日志是 MySQL 实现数据恢复的核心机制之一,主要包括 Redo Log(…...
前端面试技术性场景题
87.场景面试之大数运算:超过js中number最大值的数怎么处理 在 JavaScript 中,Number.MAX_SAFE_INTEGER(即 2^53 - 1,即 9007199254740991)是能被安全表示的最大整数。超过此值时,普通的 Number 类型会出现…...
解决CentOS 8.5被恶意扫描的问题
CentOS 8 官方仓库已停止维护(EOL),导致一些常用依赖包如fail2ban 无法正常安装。 完整解决方案: 一、问题根源 CentOS 8 官方仓库已停更:2021 年底 CentOS 8 停止维护,默认仓库的包可能无法满足依赖关系。EPEL 仓库兼容性:EPEL 仓库可能未适配 CentOS 8.5 的旧版本依赖…...
探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(二)
文章目录 2.3.3 极化编码巴氏参数与信道可靠性比特混合生成矩阵编码举例 2.3.4 极化译码最小单元译码串行抵消译码(SC译码)算法SCL译码算法 2.3.5 总结**Polar 码的优势****Polar 码的主要问题****Polar 码的应用前景** 2.3.6 **参考文档** 本博客为系列…...
机器学习准备工作
机器学习准备工作 机器学习概述常见算法动手实践 深度学习基础框架应用领域 数学基础线性代数概率论和统计学微积分 编程基础Python基础数据处理工具 项目实战入门1. Scikit-learn 示例项目2. TensorFlow/Keras 入门项目3. Kaggle 入门竞赛 进阶1. PyTorch 官方教程2. MMDetect…...
汽车智能钥匙中PKE低频天线的作用
PKE(Passive Keyless Entry)即被动式无钥匙进入系统,汽车智能钥匙中PKE低频天线在现代汽车的智能功能和安全保障方面发挥着关键作用,以下是其具体作用: 信号交互与身份认证 低频信号接收:当车主靠近车辆时…...
Codepen和tailwindcss 进行UI布局展示
html <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>设备管理仪表盘</title><script src"https://cdn.tailw…...
准备好了数据集之后,如何在ubuntu22.04上训练一个yolov8模型。
在Ubuntu 22.04上训练YOLOv8模型的步骤如下: 1. 安装依赖 首先,确保系统已安装Python和必要的库。 sudo apt update sudo apt install python3-pip python3-venv2. 创建虚拟环境 创建并激活虚拟环境: python3 -m venv yolov8_env source…...
集合框架、Collection、list、ArrayList、Set、HashSet和LinkedHashSet、判断两个对象是否相等
DAY7.1 Java核心基础 集合框架 Java 中很重要的一个知识点,实际开发中使用的频录较高,Java 程序中必备的模块 集合就是长度可以改变,可以保存任意数据类型的动态数组 最上层是一组接口,接下来是接口的实现类,第三层…...
宝塔 Linux 计划任务中添加运行项目网站PHP任务-定时任务
一、指定php版运行, cd /www/wwwroot/www.xxx.com/ && /www/server/php/56/bin/php think timedtasks start >> /tmp/timedtasks.log 2>&1 二、不指定php版 cd /www/wwwroot/www.xxx.com/ && php think timedtasks start >> …...
JDK ZOOKEEPER KAFKA安装
JDK17下载安装 mkdir -p /usr/local/develop cd /usr/local/develop 将下载的包上传服务器指定路径 解压文件 tar -zxvf jdk-17.0.14_linux-x64_bin.tar.gz -C /usr/local/develop/ 修改文件夹名 mv /usr/local/develop/jdk-17.0.14 /usr/local/develop/java17 配置环境变量…...
c++雅兰亭库 (yalantinglibs) 介绍及使用(序列化、json和结构体转换、协程
c雅兰亭库 (yalantinglibs) 介绍及使用(序列化、json和结构体转换、协程)-CSDN博客 雅兰亭库(yalantinglibs)介绍 雅兰亭库,名字很优雅,也很强大。它是阿里开源的一个现代C基础工具库的集合, 现在包括 struct_pack, struct_json, struct_xml, struct_yam…...
深度融合,智领未来丨zAIoT 全面集成 DeepSeek,助力企业迎接数据智能新时代
前言 Introduction 在数字化浪潮汹涌澎湃的当下,数据智能成为企业破局与创新的关键驱动力。zAIoT 作为云和恩墨面向 AIData 时代推出的数据智能平台软件,凭借其全面且强大的“采存算用”一体化功能体系,正在为航空航天、工业制造等领域和态势…...
类和对象—多态—案例2—制作饮品
案例描述: 制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料 利用多态技术实现本案例,提供抽象制作产品基类,提供子类制作咖啡和茶叶 思路解析: 1. 定义抽象基类 - 创建 AbstractDrinking 抽象类,该类…...
VSCode 配置优化指南:打造高效的 uni-app、Vue2/3、JS/TS 开发环境
VSCode 配置优化指南,适用于 uni-app、Vue2、Vue3、JavaScript、TypeScript 开发,包括插件推荐、设置优化、代码片段、调试配置等,确保你的开发体验更加流畅高效。 1. 安装 VSCode 如果你还未安装 VSCode,可前往 VSCode 官网 下载最新版并安装。 2. 安装推荐插件 (1) Vue…...
低代码平台的后端架构设计与核心技术解析
引言:低代码如何颠覆传统后端开发? 在传统开发模式下,一个简单用户管理系统的后端开发需要: 3天数据库设计5天REST API开发2天权限模块对接50个易出错的代码文件 而现代低代码平台通过可视化建模自动化生成,可将开发…...
Redis中多大的Key算热key,该如何解决
在 Redis 中,“热key” 是指频繁访问的 Redis 键。这些键通常会导致 Redis 服务器的性能下降,甚至可能导致 Redis 服务不可用。热key 的大小是相对的,通常来说,以下几个因素可能导致一个 Redis 键成为热key: 访问频率…...
机器学习数学基础:43.外生变量与内生变量
外生变量与内生变量:模型中的因果角色 在因果模型(像结构方程模型、回归分析这类)里,外生变量和内生变量是用来区分变量来源和相互关系的重要概念。下面从定义、实例、差异以及应用场景四个方面来详细介绍: 一、定义…...
单元测试与仿真程序之间的选择
为什么写这篇文章 现在的工作需求,让我有必要总结和整理一下。 凡事都有适用的场景。首先这里我需要提示一下,这里的信息,可能并不普适。 但是可以肯定一点的是,有些人,不论做事还是写书,上下文还没有交待…...
一周学会Flask3 Python Web开发-SQLAlchemy简介及安装
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili SQLAlchemy是Python编程语言下的一款开源软件。提供了SQL工具包及对象关系映射(ORM)工具,…...
【玩转正则表达式】正则表达式常用语法汇总
1. 基本字符 普通字符:匹配自身。例如,正则表达式hello匹配字符串中的“hello”。\d:匹配任何数字字符,相当于[0-9]。例如,\d\d\d匹配三个连续的数字。 示例:123、456 \w:匹配任何字母数字字符…...
