Go——指针和内存逃逸

news2024/9/20 23:43:41

        区别于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 main

import "fmt"

func main() {
	a := 10
	b := &a

	fmt.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 main

import "fmt"

func main() {
	a := 10
	b := &a
	fmt.Printf("type(b)=%T\n", b)
	c := *b
	fmt.Printf("c=%d, type(c)=%T\n", c, c)

	//修改值
	*b = 10
	fmt.Printf("a=%d, b=%d\n", a, *b)

	//数组指针取值
	arr := [...]int{1, 2, 3, 4, 5}
	//获得指针数组
	ptr := &arr
	fmt.Printf("%T\n", ptr)
	fmt.Println(arr)
	//修改值,就是修改原数组
	ptr[0] = 10
	fmt.Println(arr)

}

        总结:取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

变量,指针地址,指针变量,取地址,取值的互相关系和特性如下:

  1. 对变量进行取地址操作(&),可以获得这个变量的指针变量。
  2. 指针变量的值就是指针地址。
  3. 对指针变量进行取值操作(*),可以获得指针变量指向原变量的值。

指针传值示例:

package main

import "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 main

import "fmt"

func main() {
	var ptr *int
	fmt.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 main

import "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 = true
	fmt.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 main

import "fmt"

func main() {
	var b map[string]int = make(map[string]int, 10)
	b["测试"] = 100
	fmt.Println(b)
}

        1.4.3 make和new的区别

  • 二者都是用来做内存分配的。

  • make只用于slice,map和channel引用类型的初始化,返回的还是这三个引用类型本身。

  • 而new用于类型的内存分配,并且内存对应的值为类型的默认值,返回的是指向类型的指针。 

        1.5 多级指针 

        在Go语言中也存在多级指针。指针变量在内存中也需要保存,也有地址,多级指针实际就是保存指针变量的地址。

package main

import "fmt"

func main() {
	a := 10
	fmt.Printf("&a=%p\n", &a)
	//p1保存a的地址,*p1<=>a
	p1 := &a
	fmt.Printf("&p1=%p, p1=%p, *p1=%d, type(p1)=%T\n", &p1, p1, *p1, p1)

	//p2保存p1的地址 *p2为p1的值即a的地址,**p2<=>*p1<=>a
	p2 := &p1
	fmt.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语言中的指针不能进行运算。

        指针有多级指针

        引用没有多级引用。

        

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1532659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

# Django通过开关控制数据库参数(JS版)

目录 场景初始的视图层HTML部分JS代码视图层接受部分 场景 此时我的表单中有一排开关 数据库有一排状态 需求是要当开关开启时数据库state为1&#xff0c;关闭时为0 初始的视图层 将整个adv数据表返回给前端HTML def adv(request):adv_list Adv.objects.all()return rende…

语言教育App头牌Duolingo如何重新点燃用户增长350%?

Duolingo是全球最大的语言教育APP&#xff0c;拥有数亿用户&#xff0c;然而用户增长正在放缓&#xff0c;本案例以Duolingo增长 通过数据建模洞察关键指标&#xff0c;并围绕指标用增长实验驱动&#xff0c;设计植根于创新的增长模式&#xff0c;包括启动排行榜&#xff0c;重…

docker仓库登录及配置insecure-registries的方法

docker仓库登录及配置insecure-registries的方法 这篇文章主要介绍了docker仓库登录配置insecure-registries的方法,docker客户端如果配置中添加了insecure-registary配置&#xff0c;就不需要在docker 客户端配置上对应证书&#xff0c;如果不配置要在/etc/docker/certs.d/目…

【阅读论文】When Large Language Models Meet Vector Databases: A Survey

摘要 本调查探讨了大型语言模型&#xff08;LLM&#xff09;和向量数据库&#xff08;VecDB&#xff09;之间的协同潜力&#xff0c;这是一个新兴但迅速发展的研究领域。随着LLM的广泛应用&#xff0c;出现了许多挑战&#xff0c;包括产生虚构内容、知识过时、商业应用成本高昂…

流畅的 Python 第二版(GPT 重译)(十三)

第二十四章&#xff1a;类元编程 每个人都知道调试比一开始编写程序要困难两倍。所以如果你在编写时尽可能聪明&#xff0c;那么你将如何调试呢&#xff1f; Brian W. Kernighan 和 P. J. Plauger&#xff0c;《编程风格的要素》 类元编程是在运行时创建或自定义类的艺术。在 P…

ZYNQ EMIO MIO

1 概述 先来了解GPIO的BANK分布&#xff0c;在UG585文档GPIO一章中可以看到GPIO是有4个BANK&#xff0c; 注意与MIO的BANK区分。 BANK0 控制32个信号&#xff0c;BANK1控制22个信号&#xff0c;总共是MIO的54个引脚&#xff0c;也就是诸如 SPI,I2C,USB,SD 等 PS 端外设接口&am…

C语言字符函数与字符串函数:编织文字的舞会之梦(上)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 在编程的过程中&#xff0c;我们经常要处理字符以及字符串&#xff0c;为了方便操作这些字符和字符串&#xff0c;C语言标准库中提供…

jmx_prometheus_javaagent-0.19.0.jar+Prometheus+Grafana 监控Tongweb嵌入式(by lqw)

文章目录 1.思路2.部署准备3.应用jar包修改配置和导入tw嵌入式的依赖&#xff08;参考&#xff09;4.Prometheus部署5.Prometheus配置6.安装和配置Grafana 1.思路 Tongweb嵌入式最终是把依赖打入到java应用&#xff08;也就是jar包里&#xff09;&#xff0c;然后启动jar包进行…

TinTin Web3 Bounty 挑战杯开启,Sui 向你发出挑战邀请

以下文章来源于TinTinLand &#xff0c;作者TinTinLand。 2024 年开年最火的是什么&#xff1f; 对 Web3 来说&#xff0c;Bounty 任务应该是普通人获得行业“一杯羹”的重要捷径&#xff01; 通过深入学习各类 Web3 技术&#xff0c;凭借实战锻炼开发创新项目&#xff0c;就…

Linux学习:git补充与调试工具gdb

目录 1. git版本控制器&#xff08;续&#xff09;1.1 git本地仓库结构1.2 git实现版本控制与多人协作的方式1.3 git相关指令&#xff0c;多分支模型与.gitignore文件 2. gdb调试工具2.1 企业项目开发流程简述与调试的必要性2.2 bug的调试思路方法与调式工具的使用 1. git版本控…

ResNet目标检测算法实现交通灯分类

红绿灯识别方案&#xff1a;https://zhuanlan.zhihu.com/p/674791906 目录 一、制作数据集二、ResNet算法三、pytorch转onnx文件四、onnx推理测试五、onnx转mnn 一、制作数据集 1、数据集划分 将红绿灯数据集大文件夹中不同类别的小文件夹中的图片按照9&#xff1a;1进行划分…

小程序绕过 sign 签名

之前看到了一篇文章【小程序绕过sign签名思路】之前在做小程序渗透时也遇到了这种情况&#xff0c;但是直接放弃测试了&#xff0c;发现这种思路后&#xff0c;又遇到了这种情况&#xff0c;记录下过程。 并没有漏洞分享&#xff0c;仅仅是把小程序也分享出来&#xff0c;方便…

Idea 不能创建JDK1.8的spring boot项目

由于https://start.springboot.io/ 不支持JDK1.8&#xff0c;那么我们需要换idea的springboot创建源&#xff0c;需要换成 https://start.aliyun.com&#xff0c;这也是网上大部分教程说的&#xff0c;但是我这边会报这样的错误&#xff1a; Initialization failed for https:…

Go --- Go语言垃圾处理

概念 垃圾回收&#xff08;GC-Garbage Collection&#xff09;暂停程序业务逻辑SWT&#xff08;stop the world&#xff09;程序根节点&#xff1a;程序中被直接或间接引用的对象集合&#xff0c;能通过他们找出所有可以被访问到的对象&#xff0c;所以Go程序的根节点通常包括…

小程序跨端组件库 Mpx-cube-ui 开源:助力高效业务开发与主题定制

Mpx-cube-ui 是一款基于 Mpx 小程序框架的移动端基础组件库&#xff0c;一份源码可以跨端输出所有小程序平台及 Web&#xff0c;同时具备良好的拓展能力和可定制化的能力来帮助你快速构建 Mpx 应用项目。 Mpx-cube-ui 提供了灵活配置的主题定制能力&#xff0c;在组件设计开发阶…

GB28181 —— 5、C++编写GB28181设备端,完成将USB摄像头视频实时转发至GB28181服务并可播放(附源码)

被测试的USB摄像头 效果 源码说明 主要功能模拟设备端&#xff0c;完成注册、注销、心跳等&#xff0c;同时当服务端下发指令播放视频时 设备端实时读取USB摄像头视频并通过OpenCV处理后实时转ps格式后封包rtp进行推送给服务端播放。 源码 /****remark: pes头的封装,里面的具…

ETH Gas 之 Base Fee Priority Fee

前情回顾 ETH网络 之 Gas EIP-1559 EIP-1559 EIP-1559是以太坊改进提案&#xff08;Ethereum Improvement Proposal&#xff09;&#xff0c;旨在改进以太坊的交易费用机制。该提案引入了一种新的交易费用模型&#xff0c;以提高交易费用的可预测性和网络的效率。我们本文各…

敏捷开发最佳实践:学习与改进维度实践案例之会诊式培养敏捷教练

自组织团队能够定期反思并采取针对性行动来提升人效&#xff0c;但2022年的敏捷调研发现&#xff0c;70%的中国企业在学习和改进方面仍停留在团队级。本节实践案例将分享“会诊式培养敏捷教练”的具体做法&#xff0c;突出了敏捷以人为本的学习和改进&#xff0c;强调了通过人员…

​HTTP与HTTPS:网络通信的安全卫士

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

【SAP-ABAP】CO01保存时错误DBSQL_DUPLICATE_KEY_ERROR

找到该表的主键OBJNR&#xff0c;事务代码SM56中查看当前缓冲到该key的号码段&#xff0c;事务代码SNRO修改对象名称OBJNR编号范围状态。 事务代码SM13查看数据更新记录