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…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...
手动给中文分词和 直接用神经网络RNN做有什么区别
手动分词和基于神经网络(如 RNN)的自动分词在原理、实现方式和效果上有显著差异,以下是核心对比: 1. 实现原理对比 对比维度手动分词(规则 / 词典驱动)神经网络 RNN 分词(数据驱动)…...

