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)}

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

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

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

指针传值示例:

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语言中的指针不能进行运算。

        指针有多级指针

        引用没有多级引用。

        

        

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网页无插件视频播放器,支持录像、截图、音视频播放,多路播放等,提供源码下载

前言 本播放器内部采用jessibuca插件接口&#xff0c;支持录像、截图、音视频播放等功能。播放器播放基于ws流&#xff0c;分屏操作支持1分屏、4分屏、6分屏、9分屏方式。 jessibuca工作原理是通过Emscripten将音视频解码库编译成Js&#xff08;WebAssembly&#xff0c;简称was…

【国家计算机二级考试C语言.2024】学习备忘录

说明 分值 4060100 40分&#xff1a; 这里面有一大堆程序结果选这题&#xff0c;如果手速还可以。那遇到有疑问的情况就自己去倒计算器的ad E上面去打一打。能够跑出来&#xff0c;结果那是100%的没问题。 有些概念题比较讨厌&#xff0c;只能自己去记忆了。要去背诵熟熟的。…

AT32F423+DM9051 SPI以太网的MQTT例程说明

DM9051 通过 MQTT 连接阿里云平台 1. 环境简介 作业平台&#xff1a;Windows 硬体环境&#xff1a;AT-START-F423 (AT32F423) 开发板 DM9051 开发板 开发工具&#xff1a;Keil uVision 5 调试工具&#xff1a;Wireshark、串口调试助手 连接平台&#xff1a;阿里云_华东 2 (htt…

海尔洗空气型号KFR-72LW/09HAA81U1技术分析

海尔洗空气型号为:KFR-72LW/09HAA81U1 室外机包含阀门地脚的安装尺寸是960宽703高400深mm 室外机铭牌参数: 制冷运行时 制冷量:7200W(1100-9150) 最大输入功率:3550W 最大电流:16.2A 制热运行时 制热量:9800W(1100-12500) 最大输入功率:6180W 最大电流:28.1A 吸气侧允…

Apache James数据库存储用户信息的密码加密问题

项目场景 Apache James邮件服务器使用数据库来存储用户信息的密码加密问题&#xff1a; 将James的用户改为数据库存储James密码是如何加密验证的 1.将James的用户改为数据库存储 1、修改存储方式 找到james-2.3.2\apps\james\SAR-INF\config.xml 找到<users-store>标…

生产力工具|安装更新R软件(R、studio)

内容介绍&#xff1a; 安装R软件&#xff1a; 下载 R X64 3.5.1: 访问官方R网站 https://cran.r-project.org/。选择适合Windows版本的安装包。将安装包下载到您的计算机。 本地安装: 运行下载的“R-3.5.1-win.exe”文件。按照安装向导&#xff0c;选择安装路径&#xff0c;取消…

墨菲安全在软件供应链安全领域阶段性总结及思考

向外看&#xff1a;墨菲安全在软件供应链安全领域的一些洞察、思考、行动 洞察 现状&挑战&#xff1a; 过去开发安全体系是无法解决软件供应链安全问题的&#xff1b;一些过去专注开发安全领域的厂商正在错误的引导行业用开发安全思维解决软件供应链安全问题&#xff0c;治…

人事管理系统设计与实现|jsp+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW调试部署环境&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java…

双指针算法:三数之和

文章目录 一、[题目链接&#xff1a;三数之和](https://leetcode.cn/problems/3sum/submissions/515727749/)二、思路讲解三、代码演示 先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;^ _ ^<3 ❤️ ❤️ ❤️ 码字不易&#xff0c;大家的支持就是我坚持…

jmeter使用方法---自动化测试

HTTP信息头管理器 一个http请求会发送请求到服务器&#xff0c;请求里面包含&#xff1a;请求头、请求正文、请求体&#xff0c;请求头就是信息头Authorization头的主要用作http协议的认证。 Authorization的作用是当客户端访问受口令保护时&#xff0c;服务器端会发送401状态…

【RPG Maker MV 仿新仙剑 战斗场景UI (八)】

RPG Maker MV 仿新仙剑 战斗场景UI 八 状态及装备场景代码效果 状态及装备场景 本计划在战斗场景中直接制作的&#xff0c;但考虑到在战斗场景中加入太多的窗口这不太合适&#xff0c;操作也繁琐&#xff0c;因此直接使用其他场景。 代码 Pal_Window_EquipStatus.prototype.…

TXT文件内容轻松整理,一键删除文本里的多个内容,轻松整理文档!

在数字化时代&#xff0c;TXT文件已成为我们日常工作和学习的常见文档格式。然而&#xff0c;随着时间的推移&#xff0c;这些文档中可能会积累大量的冗余内容&#xff0c;如重复的文字、无用的注释或不必要的空格。手动删除这些内容不仅费时费力&#xff0c;还可能遗漏或误删重…

【高并发服务器 01】—— 基础知识回顾

接下来四周时间&#xff0c;我将会做一个高并发服务器相关的项目。 前置知识&#xff1a;操作系统系统编程、网络编程、基础的数据结构、C语言。 开发环境&#xff1a;VMware虚拟机&#xff1a;Ubuntu 20.04.6 LTS、vscode 今天先回顾一些基础知识。 1.文件与IO 标准IO&#…

uni-app从零开始快速入门

教程介绍 跨端框架uni-app作为新起之秀&#xff0c;在不到两年的时间内&#xff0c;迅速被广大开发者青睐和推崇&#xff0c;得益于它颠覆性的优势“快”&#xff0c;快到可以节省7套代码。本课程由uni-app开发者团队成员亲授&#xff0c;带领大家无障碍快速掌握完整的uni-app…

pandas的综合练习

事先说明&#xff1a; 由于每次都要导入库和处理中文乱码问题&#xff0c;我都是在最前面先写好&#xff0c;后面的代码就不在写了。要是copy到自己本地的话&#xff0c;就要把下面的代码也copy下。 # 准备工作import pandas as pd import numpy as np from matplotlib impor…

linux源配置:ubuntu、centos;lspci与lsmod命令区别

1、ubuntu源配置 1&#xff09;先查电脑版本型号: lsb_release -c2&#xff09;再编辑源更新&#xff0c;源要与上面型号对应 参考&#xff1a;https://midoq.github.io/2022/05/30/Ubuntu20-04%E6%9B%B4%E6%8D%A2%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E6%BA%90/ /etc/apt/…

【Docker】golang操作容器使用rename动态更新容器的名字

【Docker】golang操作容器使用rename动态更新容器的名字 大家好 我是寸铁&#x1f44a; 总结了一篇golang操作容器使用rename动态更新容器的名字✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天遇到一个新的需求&#xff0c;要动态改变运行中的容器名字。 可以考虑先把…

数据结构/C++:哈希表

数据结构/C&#xff1a;哈希表 哈希表概念哈希函数直接定址法除留余数法 哈希冲突闭散列 - 开放定址法基本结构查找插入删除总代码展示 开散列 - 哈希桶基本结构查找插入删除代码展示 哈希表概念 在顺序表中&#xff0c;查找一个数据的时间复杂度为O(N)&#xff1b;在平衡树这…

数据仓库相关概述

数据仓库概述 数据仓库概念 数据仓库是一个为数据分析而设计的企业级数据管理系统。数据仓库可集中、整合多个信息源的大量数据&#xff0c;借助数据仓库的分析能力&#xff0c;企业可从数据中获得宝贵的信息进而改进决策。同时&#xff0c;随着时间的推移&#xff0c;数据仓…

3个Tips,用“AI”开启新生活

相信最近&#xff0c;很多朋友们都回归到了忙碌的生活节奏中。生活模式的切换&#xff0c;或多或少会带来身体或情绪状况的起伏。新技术正在为人们生活的方方面面带来便利。3个小Tips或许能让你也从新技术中获益&#xff0c;从身到心&#xff0c;用“AI”开启新生活。 关”A…