go 指针和内存分配

定义

了解指针之前,先讲一下什么是变量。

每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像0xAFFFF(这是内存地址的十六进制表示)。

现在,要访问数据,我们需要知道存储它的地址。我们可以跟踪存储与程序相关的数据的所有内存地址。但想象一下,记住所有内存地址并使用它们访问数据会有非常困难。这就是为什么引入变量。

变量是一种占位符,用于引用计算机的内存地址,可理解为内存地址的标签。

什么是指针

指针是存储另一个变量的内存地址的变量。所以指针也是一种变量,只不过它是一种特殊变量,它的值存放的是另一个变量的内存地址。

在上面的例子中,指针p包含值0x0001,该值是变量的地址a

Go类型占用内存情况

unsafe包可以获取变量的内存使用情况

Go语言提供以下基本数字类型:

无符号整数
uint8,uint16,uint32,uint64

符号整数
int8,int16,int32,int64

实数
float32,float64 Predeclared

整数(依赖系统类型,跟系统有关)
uint,int,uintptr (指针)

32位系统

uint=uint32
int=int32
uintptr为32位的指针

64位系统

uint=uint64
int=int64
uintptr为64位的指针

示例:

package mainimport ("fmt""unsafe"
)func main() {var uint8Value uint8var uint16Value uint16var uint32Value uint32var uint64Value uint64var int8Value int8var int16Value int16var int32Value int32var int64Value int64var float32Value float32var float64Value float64fmt.Println("uint8Value = Size:", unsafe.Sizeof(uint8Value)) //uint8Value = Size: 1fmt.Println("uint16Value = Size:", unsafe.Sizeof(uint16Value)) //uint16Value = Size: 2fmt.Println("uint32Value = Size:", unsafe.Sizeof(uint32Value)) //uint32Value = Size: 4fmt.Println("uint64Value = Size:", unsafe.Sizeof(uint64Value))// uint64Value = Size: 8fmt.Println("int8Value = Size:", unsafe.Sizeof(int8Value)) //int8Value = Size: 1fmt.Println("int16Value = Size:", unsafe.Sizeof(int16Value))//int16Value = Size: 2fmt.Println("int32Value = Size:", unsafe.Sizeof(int32Value))//int32Value = Size: 4fmt.Println("int64Value = Size:", unsafe.Sizeof(int64Value)) //int64Value = Size: 8fmt.Println("float32Value = Size:", unsafe.Sizeof(float32Value)) //float32Value = Size: 4fmt.Println("float64Value = Size:", unsafe.Sizeof(float64Value))//float64Value = Size: 8}

 上面的是基本类型,接下来了解下复杂类型,以结构体类型为例

type Example struct {BoolValue  boolIntValue   int16FloatValue float32
}

该结构代表复杂类型。它代表7个字节,带有三个不同的数字表示。bool是一个字节,int16是2个字节,float32增加4个字节。但是,在此结构的内存中实际分配了8个字节。

所有内存都分配在对齐边界上,以最大限度地减少内存碎片整理。要确定对齐边界Go用于您的体系结构,您可以运行unsafe.Alignof函数。Go为64bit Darwin平台的对齐边界是8个字节。因此,当Go确定结构的内存分配时,它将填充字节以确保最终内存占用量是8的倍数。编译器将确定添加填充的位置。

什么是内存对齐呢?

内存对齐,也叫边界对齐(boundary alignment),是处理器为了提高处理性能而对存取数据的起始地址所提出的一种要求。编译器为了使我们编写的C程序更有效,就必须最大限度地满足处理器对边界对齐的要求。

 

从处理器的角度来看,需要尽可能减少对内存的访问次数以实现对数据结构进行更加高效的操作。为什么呢?因为尽管处理器包含了缓存,但它在处理数据时还得读取缓存中的数据,读取缓存的次数当然是越少越好!如上图所示,在采用边界对齐的情况下,当处理器需要访问a_变量和b_变量时都只需进行一次存取(图中花括号表示一次存取操作)。若不采用边界对齐,a_变量只要一次处理器操作,而b_变量却至少要进行两次操作。对于b_,处理器还得调用更多指令将其合成一个完整的4字节,这样无疑大大降低了程序效率。

以下程序显示Go插入到Example类型struct的内存占用中的填充:

package mainimport ("fmt""unsafe"
)type Example struct {BoolValue  boolIntValue   int16FloatValue float32
}func main() {example := &Example{BoolValue:  true,IntValue:   10,FloatValue: 3.141592,}exampleNext := &Example{BoolValue:  true,IntValue:   10,FloatValue: 3.141592,}alignmentBoundary := unsafe.Alignof(example)sizeBool := unsafe.Sizeof(example.BoolValue)offsetBool := unsafe.Offsetof(example.BoolValue)sizeInt := unsafe.Sizeof(example.IntValue)offsetInt := unsafe.Offsetof(example.IntValue)sizeFloat := unsafe.Sizeof(example.FloatValue)offsetFloat := unsafe.Offsetof(example.FloatValue)sizeBoolNext := unsafe.Sizeof(exampleNext.BoolValue)offsetBoolNext := unsafe.Offsetof(exampleNext.BoolValue)fmt.Printf("example Size: %d\n", unsafe.Sizeof(example))fmt.Printf("Alignment Boundary: %d\n", alignmentBoundary)fmt.Printf("BoolValue = Size: %d Offset: %d Addr: %v\n",sizeBool, offsetBool, &example.BoolValue)fmt.Printf("IntValue = Size: %d Offset: %d Addr: %v\n",sizeInt, offsetInt, &example.IntValue)fmt.Printf("FloatValue = Size: %d Offset: %d Addr: %v\n",sizeFloat, offsetFloat, &example.FloatValue)fmt.Printf("Next = Size: %d Offset: %d Addr: %v\n",sizeBoolNext, offsetBoolNext, &exampleNext.BoolValue)}

输出:

example Size: 8
Alignment Boundary: 8
BoolValue = Size: 1 Offset: 0 Addr: 0xc00004c080
IntValue = Size: 2 Offset: 2 Addr: 0xc00004c082
FloatValue = Size: 4 Offset: 4 Addr: 0xc00004c084
Next = Size: 1 Offset: 0 Addr: 0xc00004c088

类型结构的对齐边界是预期的8个字节。

大小值显示将读取和写入该字段的内存量。正如所料,大小与类型信息一致。

偏移值显示进入内存占用的字节数,我们将找到该字段的开头。

地址是可以找到内存占用内每个字段的开头的地方。

我们可以看到Go在BoolValue和IntValue字段之间填充1个字节。偏移值和两个地址之间的差异是2个字节。您还可以看到下一个内存分配是从结构中的最后一个字段开始4个字节。

指针的使用

声明一个指针

使用以下语法声明类型为T的指针

var p *int

 指针的零值是nil。这意味着任何未初始化的指针都将具有该值nil。让我们看一个完整的例子

package main
import "fmt"func main() {var p *int&p=1
}

注意:当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。

示例:

package mainfunc main() {var p *int*p = 1 //panic: runtime error: invalid memory address or nil pointer dereference}

解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量

示例:

import "fmt"func main() {var p *intvar m intp = &m*p = 1fmt.Println("m=", m)fmt.Println("p=", p)
}

或还可以使用内置new()函数创建指针。该new()函数将类型作为参数,分配足够的内存以容纳该类型的值,并返回指向它的指针。

import "fmt"func main() {var p *intp = new(int)*p = 1fmt.Println("p=", *p)
}

初始化指针

您可以使用另一个变量的内存地址初始化指针。可以使用&运算符检索变量的地址

var x = 100
var p *int = &x

注意我们如何使用&带变量的运算符x来获取其地址,然后将地址分配给指针p

就像Golang中的任何其他变量一样,指针变量的类型也由编译器推断。所以你可以省略p上面例子中指针的类型声明,并像这样写

var p = &a

取消引用指针

您可以*在指针上使用运算符来访问存储在指针所指向的变量中的值。这被称为解除引用间接

package main
import "fmt"func main() {var a = 100var p = &afmt.Println("a = ", a)fmt.Println("p = ", p)fmt.Println("*p = ", *p)
}

输出:

a =  100
p =  0xc00004c080
*p =  100

您不仅可以使用*运算符访问指向变量的值,还可以更改它。以下示例a通过指针设置存储在变量中的值p

package main
import "fmt"func main() {var a = 1000var p = &afmt.Println("a (before) = ", a)// Changing the value stored in the pointed variable through the pointer*p = 2000fmt.Println("a (after) = ", a)
}

输出:

a (before) =  1000
a (after) =  2000

指针指向指针

指针可以指向任何类型的变量。它也可以指向另一个指针。以下示例显示如何创建指向另一个指针的指针

package main
import "fmt"func main() {var a = 7.98var p = &avar pp = &pfmt.Println("a = ", a)fmt.Println("address of a = ", &a)fmt.Println("p = ", p)fmt.Println("address of p = ", &p)fmt.Println("pp = ", pp)// Dereferencing a pointer to pointerfmt.Println("*pp = ", *pp)fmt.Println("**pp = ", **pp)
}

Go中没有指针算术

如果您使用过C / C ++,那么您必须意识到这些语言支持指针算法。例如,您可以递增/递减指针以移动到下一个/上一个内存地址。您可以向/从指针添加或减去整数值。您也可以使用关系运算符比较两个三分球==<>等。

但Go不支持对指针进行此类算术运算。任何此类操作都将导致编译时错误

package mainfunc main() {var x = 67var p = &xvar p1 = p + 1 // Compiler Error: invalid operation
}

但是,您可以使用==运算符比较相同类型的两个指针的相等性。

package main
import "fmt"func main() {var a = 75var p1 = &avar p2 = &aif p1 == p2 {fmt.Println("Both pointers p1 and p2 point to the same variable.")}
}

Go中传递简单类型

import "fmt"func main() {p := 5change(&p)fmt.Println("p=", p)//p= 0
}
func change(p *int) {*p = 0
}

Go中所有的都是按值传递,对于复杂类型,传的是指针的拷贝

package mainimport "fmt"func main() {var m map[string]intm = map[string]int{"one": 1, "two": 2}n := mfmt.Printf("%p\n", &m) //0xc000074018fmt.Printf("%p\n", &n) //0xc000074020fmt.Println(m)         // map[two:2 one:1]fmt.Println(n)         //map[one:1 two:2]changeMap(m)fmt.Printf("%p\n", &m) //0xc000074018fmt.Printf("%p\n", &n) //0xc000074020fmt.Println(m)         //map[one:1 two:2 three:3]fmt.Println(n)         //map[one:1 two:2 three:3]
}
func changeMap(m map[string]int) {m["three"] = 3fmt.Printf("changeMap func %p\n", m) //changeMap func 0xc000060240
}

直接传指针 也是传指针的拷贝

package mainimport "fmt"func main() {var m map[string]intm = map[string]int{"one": 1, "two": 2}n := mfmt.Printf("%p\n", &m) //0xc000074018fmt.Printf("%p\n", &n) //0xc000074020fmt.Println(m)         // map[two:2 one:1]fmt.Println(n)         //map[one:1 two:2]changeMap(&m)fmt.Printf("%p\n", &m) //0xc000074018fmt.Printf("%p\n", &n) //0xc000074020fmt.Println(m)         //map[one:1 two:2 three:3]fmt.Println(n)         //map[two:2 three:3 one:1]
}
func changeMap(m *map[string]int) {//m["three"] = 3 //这种方式会报错 invalid operation: m["three"] (type *map[string]int does not support indexing)(*m)["three"] = 3                    //正确fmt.Printf("changeMap func %p\n", m) //changeMap func 0x0
}

总结:

  • Go 不能进行指针运算。
  • 指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。
  • 指针也是一种类型,不同于一般类型,指针的值是地址,这个地址指向其他的内存,通过指针可以读取其所指向的地址所存储的值。
  • 函数方法的接受者,也可以是指针变量。简单类型和复杂类型在传递的时候不同,复杂类型传值或传指针都是指针拷贝。
  • 只声明未赋值的变量,golang都会自动为其初始化为零值,基础数据类型的零值比较简单,引用类型和指针的零值都为nil,nil类型不能直接赋值,因此需要通过new开辟一个内存,或指向一个变量。

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

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

相关文章

程序员们应注意的行业特有的法律问题

大家好&#xff0c;我是不会魔法的兔子&#xff0c;是一枚执业律师&#xff0c;持续分享技术类行业项目风险及预防的问题。 一直以来兔子都在以大家做项目时候会遇到的风险问题做分享&#xff0c;最近有个念头一直挥之不去&#xff0c;就是要不要给我们广大的程序员们也分享一…

【接口】HTTP(1)|请求|响应

1、概念 Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;用于从万维网&#xff08;就是www&#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP协议是基于TCP的应用层协议&#xff0c;它不关心数据传输的细节&#xff0c;主要是用来规定客户端和…

【C++练级之路】【Lv.18】哈希表(哈希映射,光速查找的魔法)

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、哈希1.1 哈希概念1.2 哈希函数1.3 哈希冲突 二、闭散列2.1 数据类型2.2 成员变量2.3 默认成员函数2.…

【yy讲解PostCSS是如何安装和使用】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

深度学习:神经网络模型的剪枝和压缩简述

深度学习的神经网路的剪枝和压缩&#xff0c;大致的简述&#xff0c; 主要采用&#xff1a; network slimming&#xff0c;瘦身网络... 深度学习网络&#xff0c;压缩的主要方式&#xff1a; 1.剪枝&#xff0c;nerwork pruing&#xff0c; 2.稀疏表示&#xff0c;sparse rep…

KV260 BOOT.BIN更新 ubuntu22.04 netplan修改IP

KV260 2022.2设置 BOOT.BIN升级 KV260开发板需要先更新BOOT.BIN到2022.2版本&#xff0c;命令如下&#xff1a; sudo xmutil bootfw_update -i “BOOT-k26-starter-kit-202305_2022.2.bin” 注意BOOT.BIN应包含全目录。下面是更新到2022.1 FW的示例&#xff0c;非更新到2022.…

八数码问题——A*算法的应用(A-Star)

文章目录 1 问题描述2 启发式搜索3 A*算法3.1 参考网址3.2 是什么3.3 为什么A*算法适用于八数码问题3.4 A* 算法的基本框架 4 A* 算法如何解决八数码问题4.1 八数码状态的存储4.2 启发式函数4.3 构造目标状态元素位置的字典4.4 在二维列表中查找目标元素4.5 A* 算法主体4.6 路径…

Git 术语及中英文对照

完毕&#xff01;&#xff01;感谢您的收看 ----------★★历史博文集合★★---------- 我的零基础Python教程&#xff0c;Python入门篇 进阶篇 视频教程 Py安装py项目 Python模块 Python爬虫 Json Xpath 正则表达式 Selenium Etree CssGui程序开发 Tkinter Pyqt5 列表元组字…

Ubuntu22.04安装Anaconda

一、下载安装包 下载地址&#xff1a;https://www.anaconda.com/download#Downloads 参考&#xff1a;Ubuntu下安装Anaconda的步骤&#xff08;带图&#xff09; - 知乎 下载Linux 64-Bit (x86) installer 二、安装 在当前路径下&#xff0c;执行命令&#xff1a; bash Ana…

了解以太坊虚拟机(EVM)

了解以太坊虚拟机&#xff08;EVM&#xff09; 以太坊虚拟机&#xff08;Ethereum Virtual Machine&#xff0c;简称EVM&#xff09;是以太坊网络的核心组件之一&#xff0c;它承担着智能合约执行的重要任务 特点 智能合约执行环境&#xff1a;EVM提供了一个安全的环境&#xf…

vxe-table表格组件给row-style和cell-style等修改样式无效的问题,例如:background-color

因情况而异吧&#xff0c;我是因为使用了jsx jsx的语法规则之一&#xff1a;内联样式&#xff0c;要用 style{{key:value}}的形式去写。有的需要以小驼峰式写,例如&#xff1a;font-size需要写成 fontSize background-color就是backgroundColor

【智能排班系统】快速消费线程池

文章目录 线程池介绍线程池核心参数核心线程数&#xff08;Core Pool Size&#xff09;最大线程数&#xff08;Maximum Pool Size&#xff09;队列&#xff08;Queue&#xff09;线程空闲超时时间&#xff08;KeepAliveTime&#xff09;拒绝策略&#xff08;RejectedExecutionH…

Raven:一款功能强大的CICD安全分析工具

关于Raven Raven是一款功能强大的CI/CD安全分析工具&#xff0c;该工具旨在帮助广大研究人员对GitHub Actions CI工作流执行大规模安全扫描&#xff0c;并将发现的数据解析并存储到Neo4j数据库中。 Raven&#xff0c;全称为Risk Analysis and Vulnerability Enumeration for C…

jQuery(一)

文章目录 1. 基本介绍2.原理示意图3.快速入门1.下载jQuery2.创建文件夹&#xff0c;放入jQuery3.引入jQuery4.代码实例 4.jQuery对象与DOM对象转换1.基本介绍2.dom对象转换JQuery对象3.JQuery对象转换dom对象4.jQuery对象获取数据获取value使用val&#xff08;&#xff09;获取…

HCIA-RS基础-VLAN路由

目录 VLAN 路由1. 什么是 VLAN 路由2. VLAN 路由的原理及配置3. VLAN 的缺点和 VLAN Trunking4. 单臂路由配置 总结 VLAN 路由 1. 什么是 VLAN 路由 VLAN 路由是指在虚拟局域网&#xff08;VLAN&#xff09;之间进行路由转发的过程。传统的 VLAN 配置只能在同一个 VLAN 内进行…

【LeetCode热题100】51. N 皇后(回溯)

一.题目要求 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方…

程序员沟通之道:TCP与UDP之辩,窥见有效沟通的重要性(day19)

程序员沟通的重要性&#xff1a; 今天被师父骂了一顿&#xff0c;说我不及时回复他&#xff0c;连最起码的有效沟通都做不到怎么当好一个程序员&#xff0c;想想还挺有道理&#xff0c;程序员需要知道用户到底有哪些需求&#xff0c;用户与程序员之间的有效沟通就起到了关键性作…

Spring Boot 整合 OSS 实现文件上传

一、开通 OSS OSS 也就是 Object Storage Service&#xff0c;是阿里云提供的一套对象存储服务&#xff0c;国内的竞品还有七牛云的 Kodo和腾讯云的COS。 第一步&#xff0c;登录阿里云官网&#xff0c;搜索“OSS”关键字&#xff0c;进入 OSS 产品页。 第二步&#xff0c;如果…

[Python学习篇] Python解释器

解释器的作用 Python解释器&#xff08;Interpreter&#xff09;的作用&#xff0c;通俗理解&#xff0c;就是起到一个翻译的作用&#xff0c;把程序员所编写的代码翻译为计算机能读懂执行的代码。简单地说&#xff0c;Python解释器对输入的Python代码进行解释和执行。Python解…

(科研实践篇)大模型相关知识

1.embedding 1.介绍&#xff1a; embedding就是用一个低纬的向量表示一个物品。而这个embedding向量的实质就是使距离相似的向量所对应的物品具有相似的含义&#xff08;参考皮尔逊算法和cos余弦式子&#xff1a;计算相似度&#xff09;简单来说&#xff0c;就是用空间去表示…