Go——指针和内存逃逸
区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。
要搞明白Go语言中的指针概念需要先知道3个概念:指针地址,指针类型和指针取值。
一. Go语言的指针
Go语言中的函数传参都是值拷贝,当我们想修改某个变量时,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号'*'(解引用,根据地址取值)和'&'(取地址)。
1.1 指针地址和指针类型
每个变量运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面,对变量进行取地址操作。Go语言中的值类型(int,float,bool,string,array,struct)都有对应的指针类型,如*int,*int64,*string,*[5]int等。
指针就是地址,指针类型是一个类型,比如:*int(整型指针类型)。
取变量指针的语法:
ptr := &v
其中:
v:代表被取地址的变量,类型T
ptr:用于接收地址的变量,ptr的类型就是*T,称作T的指针类型。*代表指针。
举个例子:
package mainimport "fmt"func main() {a := 10b := &afmt.Printf("a=%d, &a=%p, type(a)=%T\n", a, &a, a)fmt.Printf("b=%p, &b=%p, type(b)=%T\n", b, &b, b)
}
b := &a图示:
1.2 指针取值
对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值。
但是数组指针不需要使用*符号,直接索引就可以取值。
package mainimport "fmt"func main() {a := 10b := &afmt.Printf("type(b)=%T\n", b)c := *bfmt.Printf("c=%d, type(c)=%T\n", c, c)//修改值*b = 10fmt.Printf("a=%d, b=%d\n", a, *b)//数组指针取值arr := [...]int{1, 2, 3, 4, 5}//获得指针数组ptr := &arrfmt.Printf("%T\n", ptr)fmt.Println(arr)//修改值,就是修改原数组ptr[0] = 10fmt.Println(arr)}
总结:取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。
变量,指针地址,指针变量,取地址,取值的互相关系和特性如下:
- 对变量进行取地址操作(&),可以获得这个变量的指针变量。
- 指针变量的值就是指针地址。
- 对指针变量进行取值操作(*),可以获得指针变量指向原变量的值。
指针传值示例:
package mainimport "fmt"func test1(x int) {x = 100
}func test2(x *int) {*x = 200
}func main() {x := 10//值拷贝,没有修改实参,修改的是形参test1(x)fmt.Println(x)//传入指针,修改了传入变量test2(&x)fmt.Println(x)
}
1.3 空指针
-
当一个指针被定义没有分配到任何变量时,它的值为nil
-
空指针判断
package mainimport "fmt"func main() {var ptr *intfmt.Println(ptr)fmt.Printf("ptr的值是%s\n", ptr)if ptr == nil {fmt.Println("空值")} else {fmt.Println("非空")}
}
1.4 new和make
下面这个例子报panic错的原因是:在Go语言中对于引用类型的变量,我们使用的时候不仅要声明它,我们还需要为它分配内存,都在我们的值没有办法存储。而对于值类型的声明不需要分配内存空间,是因为他们在声明的时候已经默认分配好了内存空间。这时的指针相当于是一个野指针。
下面的a指针变量和b变量(map引用类型),只进行了声明(声明之后默认给初始值,指针初始值为nil,map初始值为map[]等),没有分配内存,a的值为nil,b的值为map[](map底层实际是一个指向hmap的指针,声明实际指针也是nil)。不能使用。
分配内存,需要用到Go语言内建的new和make函数。
1.4.1 new
new是一个内置函数,它的函数签名如下:
func new(Type) *Type
其中:
- Type表示类型,new函数只接受一个参数,这个参数是一个类型。
- *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的默认值。举个例子:
package mainimport "fmt"func main() {a := new(int)b := new(bool)//默认值fmt.Printf("*a=%d, type(a)=%T\n", *a, a)fmt.Printf("*b=%v, type(b)=%T\n", *b, b)*a = 10*b = truefmt.Println(*a, *b)
}
上面报错的例子中,由于var a *int只是声明没有初始化分配内存,是一个野指针,不能使用。初始化需要使用new函数 var a *int = new(int),之后才能使用。
1.4.2 make
make也是用来内存分配的,区别于new,它只用于slice,map以及chan(管道)的内存创建,而它返回的类型就是这三个类型本身,而不是他们的指针类型。因为这三个类型都是引用类型,所以就没有必要返回指针类型。
函数签名:
func make(t Type, size ...IntegerType) Type
其中:
- t Type表示类型。
- size ...IntegerType:是一个可变参数,int类型,可以传多个值,一般传入类型大小。
- 返回值类型Type,不是指针,直接是引用类型。
make函数是无可替代的,我们在使用slice,map以及channel的时候,都需要使用make进行初始化,然后才可以对他们进行操作。
上面例子中的var b map[string]int只是声明了变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行赋值:
package mainimport "fmt"func main() {var b map[string]int = make(map[string]int, 10)b["测试"] = 100fmt.Println(b)
}
1.4.3 make和new的区别
-
二者都是用来做内存分配的。
-
make只用于slice,map和channel引用类型的初始化,返回的还是这三个引用类型本身。
-
而new用于类型的内存分配,并且内存对应的值为类型的默认值,返回的是指向类型的指针。
1.5 多级指针
在Go语言中也存在多级指针。指针变量在内存中也需要保存,也有地址,多级指针实际就是保存指针变量的地址。
package mainimport "fmt"func main() {a := 10fmt.Printf("&a=%p\n", &a)//p1保存a的地址,*p1<=>ap1 := &afmt.Printf("&p1=%p, p1=%p, *p1=%d, type(p1)=%T\n", &p1, p1, *p1, p1)//p2保存p1的地址 *p2为p1的值即a的地址,**p2<=>*p1<=>ap2 := &p1fmt.Printf("&p2=%p, p2=%p, *p2=%p, **p=%d type(p2)=%T\n", &p2, p2, *p2, **p2, p2)}
二.内存逃逸
查看内存逃逸信息命令:
go build -gcflags "-m" project.go
go run -gcflags "-m -l" project.go参数:
-m:打印逃逸信息
-l:禁止内联编译
2.1 现象
- make,new和函数内部的变量保存在哪里?
make和new出来的变量保存在堆上。
而函数内部定义的变量需要通过逃逸分析来决定保存位置。
现象:通过内存逃逸命令我们可以看到变量c被保存到了堆上。
原因:由于test()函数返回指针变量(&c),Go编译器认为外部还会使用到变量c,如果将其回收,返回的指针就变成了野指针,获取不到对应值了。于是将其分配到了堆上。这个操作时Go编译器做的。
对比C/C++,将变量分配到堆上的操作需要程序员来做,否则变量被回收,返回的指针编程了野指针。
2.1 逃逸分析定义
Go语言的逃逸分析是指:Go编译器用来决定变量存储位置的过程。
2.2 逃逸分析标准
- 如果一个变量只在函数内部使用,并且没有其他引用,那么它通常会被分配到栈上。
- 如果变量在函数返回后仍然被引用,会造成逃逸
- 栈空间不足,会造成逃逸
- 动态类型逃逸,不确定变量类型
当函数参数为"interface{}"类型,如最常用的fmt.Println(a ...interface{}),编译期间很难确定其参数的具体类型,也会发生逃逸。
- 不确定长度大小,会发生逃逸
- 闭包引用对象发生逃逸
2.3 总结
-
逃逸分析在编译阶段完成
-
逃逸分析目的是决定内分配地址是栈还是堆
-
栈上分配内存比在堆中分配内存有更高的效率
-
栈上分配的内存不需要GC处理,堆上分配的内存使用完毕会交给GC处理
在实际中,应该尽量避免逃逸。栈中的变量不需要gc回收。同时栈的分配比堆快,性能好。
另外,还可以进行同步消除,如果定义的对象的方法上有同步锁,但在运行时却只有一个线程在访问,此时逃逸分析后的机器码会去掉同步锁运行
三.引用类型和指针类型区别
引用类型和指针类型是两个不同的类型。与C++中的引用相似,但也有很多不同的地方。
区别:
- 从定义上
引用类型包括slice,map,channel,interface等,它们实际是对底层数据结构的抽象,通过这些类型可以直接操作底层数据结构的元素。
指针是一个保存变量地址的变量,它指向变量在内存中的位置。
- 从传递方式上
引用类型在函数调用时使用的是引用传递,函数在内部修改参数,会影响实际参数的值,可以直接使用。
指针类型虽然说也是引用传递,但由于他是间接访问,在函数内部对指针进行修改不会修改实际参数值,而需要进行解引用。
- 从性质上
引用类型是原变量的别名,没有自己独立的空间。
指针类型的变量是一个实体,保存另一个变量的地址。但是Go语言中的指针不能进行运算。
指针有多级指针
引用没有多级引用。
相关文章:

Go——指针和内存逃逸
区别于C/C中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。 要搞明白Go语言中的指针概念需要先知道3个概念:指针地址,指针类型和指针取值。 一. Go语言的指针 Go语言中的函数传参都是值拷贝,当我们想修改某个…...

PTA L2-032 彩虹瓶
彩虹瓶的制作过程(并不)是这样的:先把一大批空瓶铺放在装填场地上,然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。 假设彩虹瓶里要按顺序装 N 种颜色的小球(不妨将顺序就编号为 1 到 N)。现在工…...
Spring和Spring Boot之间的区别
Spring和Spring Boot之间的区别 不仅仅体现在操作简化、配置方式以及开发速度上,还有以下几个方面: 模块化和功能范围: Spring是一个完整的框架,提供了各种各样的功能,包括依赖注入、面向切面编程、数据访问、事务管…...

海外客户获取难?海外云手机助力电商引流!
海外电商面临的市场竞争激烈,如何在海外市场获客成为了摆在许多卖家面前的难题。而在这个问题的解决方案中,海外云手机崭露头角,成为助力电商引流的新利器。 在当前市场中,云手机主要用于游戏挂机,但其潜力在海外电商领…...
什么情况下 C++ 需要垃圾处理机制?
C,作为一种以性能和灵活性著称的编程语言,历来以其严谨的手动内存管理而闻名。然而,尽管C提供了丰富的工具如RAII(Resource Acquisition Is Initialization)原则、智能指针等来协助开发者有效地管理内存,但…...

流畅的 Python 第二版(GPT 重译)(七)
第十三章:接口、协议和 ABCs 针对接口编程,而不是实现。 Gamma、Helm、Johnson、Vlissides,《面向对象设计的第一原则》 面向对象编程关乎接口。在 Python 中理解类型的最佳方法是了解它提供的方法——即其接口——如 “类型由支持的操作定义…...
vue项目中使用vue-pdf或pdf.Js,实现在页面上预览pdf内容
一。vue-pdf 1. 安装vue-pdf npm install --save vue-pdf2.页面引入 js部分 import pdf from "vue-pdf";data(){return {pdfUrl: "",pageTotal: 0,} }mounted(){this.pdfUrl pdf.createLoadingTask(pdf文件路径url);// 获取页码this.pdfUrl.promise…...

为什么静态成员函数不能是虚函数
在面向对象编程中,静态成员函数和虚函数都是常见的概念,但它们之间存在着本质上的差异。由于其特性上的差异,静态成员函数不能声明为虚函数。下面我们来探讨一下为什么静态成员函数不能是虚函数。 我在网上查到最多的说法是静态函数没有this指…...
python环境移植(本机windows到离线windows环境)
Python环境整体迁移(包括无网络情况)_python 迁移 新老无法联网-CSDN博客...
蓝桥杯day9刷题日记
P8649 [蓝桥杯 2017 省 B] k 倍区间 思路:前缀和的题,对k取余相同的数就可以得到k的倍数 #include <iostream> #include <string> using namespace std; long long ans; int n,k; long long q[100010]; long long sum[100010];int main() …...

阿里云数据库Cassandra的产品价格
本文介绍阿里云数据库Cassandra的价格。 支持的地域 当前开通的地域如下: 中国站点:华东1(杭州)、华东2(上海)、华南1(深圳)、华北1(青岛)、华北2ÿ…...
离散制造企业MES与流程企业MES的区别
制造行业根据加工过程管控主要分为两大类:离散型与流程型。 离散型主要是通过对原材料的物理形状改进或组合,使其成为产品并增值,如机械加工、家用电器、电子电气行业等。 流程型则主要是采用物料或化学的方法对原材料进行混合、分离、加热…...
中国象棋C++
题目描述 在中国象棋中正所谓新手玩车,熟手玩炮,老手玩马,由此可见象棋中炮的地位还是比较高的。 给定一个nm的棋盘,全部摆满炮,我们视所有炮都不属于同一阵营,他们之间可以相互攻击但不能不进行攻击直接移…...

记录一下目前为止的算法成长
每日笔记 复习曲线 间隔1天、3天、7天、15天、30天,然后以一个月为周期复习 2023. 12. 24 一定要每天早中晚都要复习一下 早中午每段一两道, 而且一定要是同一个类型, 不然刷起来都没有意义 11.29 开始向着面试刷题跟进! 每天刷4题左右 ,一周之内一定要是统一类…...
AI大模型学习在数控系统工艺优化与智能制造中的应用
目录 1.工艺优化: 2.预测维护: 3.质量控制: 4.自动编程: 5.人机协作: 6.系统集成: AI大模型学习在数控系统工艺优化与智能制造中的应用主要体现在以下几个方面: 1.工艺优化: …...

安卓findViewById 的优化方案:ViewBinding与ButterKnife(一)
好多小伙伴现在还用findViewById来获取控件的id, 在这里提供俩种替代方案:ViewBinding与ButterKnife; 先来说说ButterKnife ButterKnife ButterKnife是一个专注于Android系统的View注入框架,在过去的项目中总是需要很多的findViewById来查…...

map和set(三)——红黑树
1、红黑树的概念及性质 1.1概念 概念: 红黑树是一种二叉搜索树,以颜色(Red or Black)互斥来限制每条路径不会比另外的路径长出两倍,来达到近似平衡 1.2性质 红黑树的性质: 每个节点不是黑色就是红色根节点是黑色的如果一个节点是…...
Day26 HashMap
Day26 HashMap 文章目录 Day26 HashMap一、应用场景二、特点三、基本用法四、面试题 一、应用场景 1、概念: HashMap是Java集合框架中的一种实现类,用于存储键值对。 2、好处: HashMap是一个常用的集合类,适用于需要快速查找和插…...

某蓝队面试经验
背景 据小道消息说今年的国护疑似提前到了五月份,所以最近也是HW面试的一个高峰期啊,这里分享一下上次长亭的蓝队面试问题(附本人的回答,仅供参考) 面试问答 1、谈谈作为蓝队护网过程使用过厂商的设备 这里我回答的…...

【Linux】 centos7安装卸载SQL server(2017、2019)
一、安装配置 准备一个基础Linux配置: 内存为20GB 运行内存为2GB的系统(数据库小于2GB安装不了) 1、网络配置 我们需要进行网络的连接 进入 cd /ect/sysconfig/network-script/ 编辑文件ifcfg-ens33 vi ifcfg-ens33 Insert键进行编辑 把ONBOO…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...