go 指针和内存分配

news2024/11/17 4:38:53

定义

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

每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像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 main

import (
   "fmt"
   "unsafe"
)

func main() {
   var uint8Value uint8
   var uint16Value uint16
   var uint32Value uint32
   var uint64Value uint64
   var int8Value int8
   var int16Value int16
   var int32Value int32
   var int64Value int64

   var float32Value float32
   var float64Value float64

   fmt.Println("uint8Value = Size:", unsafe.Sizeof(uint8Value)) //uint8Value = Size: 1
   fmt.Println("uint16Value = Size:", unsafe.Sizeof(uint16Value)) //uint16Value = Size: 2
   fmt.Println("uint32Value = Size:", unsafe.Sizeof(uint32Value)) //uint32Value = Size: 4
   fmt.Println("uint64Value = Size:", unsafe.Sizeof(uint64Value))// uint64Value = Size: 8

   fmt.Println("int8Value = Size:", unsafe.Sizeof(int8Value)) //int8Value = Size: 1
   fmt.Println("int16Value = Size:", unsafe.Sizeof(int16Value))//int16Value = Size: 2
   fmt.Println("int32Value = Size:", unsafe.Sizeof(int32Value))//int32Value = Size: 4
   fmt.Println("int64Value = Size:", unsafe.Sizeof(int64Value)) //int64Value = Size: 8

   fmt.Println("float32Value = Size:", unsafe.Sizeof(float32Value)) //float32Value = Size: 4
   fmt.Println("float64Value = Size:", unsafe.Sizeof(float64Value))//float64Value = Size: 8

}

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

type Example struct {
   BoolValue  bool
   IntValue   int16
   FloatValue 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 main

import (
   "fmt"
   "unsafe"
)

type Example struct {
   BoolValue  bool
   IntValue   int16
   FloatValue 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 main

func main() {
   var p *int

   *p = 1 //panic: runtime error: invalid memory address or nil pointer dereference

}

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

示例:

import "fmt"

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

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

import "fmt"

func main() {
   var p *int

   p = new(int)
   *p = 1
   fmt.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 = 100
    var p = &a

    fmt.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 = 1000
    var p = &a

    fmt.Println("a (before) = ", a)

    // Changing the value stored in the pointed variable through the pointer
    *p = 2000

    fmt.Println("a (after) = ", a)
}

输出:

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

指针指向指针

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

package main
import "fmt"

func main() {
    var a = 7.98
    var p = &a
    var pp = &p

    fmt.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 pointer
    fmt.Println("*pp = ", *pp)
    fmt.Println("**pp = ", **pp)
}

Go中没有指针算术

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

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

package main

func main() {
    var x = 67
    var p = &x

    var p1 = p + 1 // Compiler Error: invalid operation
}

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

package main
import "fmt"

func main() {
    var a = 75
    var p1 = &a
    var p2 = &a

    if p1 == p2 {
        fmt.Println("Both pointers p1 and p2 point to the same variable.")
    }
}

Go中传递简单类型

import "fmt"

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

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

package main

import "fmt"

func main() {
    var m map[string]int
    m = map[string]int{"one": 1, "two": 2}
    n := m
    fmt.Printf("%p\n", &m) //0xc000074018
    fmt.Printf("%p\n", &n) //0xc000074020
    fmt.Println(m)         // map[two:2 one:1]
    fmt.Println(n)         //map[one:1 two:2]
    changeMap(m)
    fmt.Printf("%p\n", &m) //0xc000074018
    fmt.Printf("%p\n", &n) //0xc000074020
    fmt.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"] = 3
    fmt.Printf("changeMap func %p\n", m) //changeMap func 0xc000060240
}

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

package main

import "fmt"

func main() {
    var m map[string]int
    m = map[string]int{"one": 1, "two": 2}
    n := m
    fmt.Printf("%p\n", &m) //0xc000074018
    fmt.Printf("%p\n", &n) //0xc000074020
    fmt.Println(m)         // map[two:2 one:1]
    fmt.Println(n)         //map[one:1 two:2]
    changeMap(&m)
    fmt.Printf("%p\n", &m) //0xc000074018
    fmt.Printf("%p\n", &n) //0xc000074020
    fmt.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.coloradmin.cn/o/1567986.html

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

相关文章

讲讲你对数据结构-线性表了解多少?

线性表 - 数组和矩阵 当谈到线性表时&#xff0c;数组和矩阵是两种常见的数据结构。 数组&#xff08;Array&#xff09;&#xff1a; 数组是有序的元素集合&#xff0c;可以通过索引来访问和操作其中的元素。它是最简单、最基本的数据结构之一。数组的特点包括&#xff1a; …

ctf_show笔记篇(web入门---SSRF)

ssrf简介 ssrf产生原理&#xff1a; 服务端存在网络请求功能/函数&#xff0c;例如&#xff1a;file_get_contens()这一类类似于curl这种函数传入的参数用户是可控的没有对用户输入做过滤导致的ssrf漏洞 ssrf利用: 用于探测内网服务以及端口探针存活主机以及开放服务探针是否存…

计算机网络:局域网的数据链路层

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【2024红明谷】三道Web题目的记录

红明谷 文章目录 红明谷Web1 | SOLVED LaterWeb2 | UNSOLVEDWeb3 | SOLVED 容器已经关咯&#xff0c;所以有些场景只能靠回忆描述啦&#xff0c;学习为主&#xff0c;题目只是一个载体~ 本次比赛学习为主&#xff0c;确实再一次感受到久违的web题目的魅力了&#xff0c;可能也是…

C++实现二叉搜索树的增删查改(非递归玩法)

文章目录 一、二叉搜索树的概念结构和时间复杂度二、二叉搜索树的插入三、二叉搜索树的查找四、二叉搜索树的删除&#xff08;最麻烦&#xff0c;情况最多&#xff0c;一一分析&#xff09;3.1首先我们按照一般情况下写&#xff0c;不考虑特殊情况下4.1.1左为空的情况&#xff…

小波降噪基础-python版本

这篇小文将使用小波多分辨分析对一个简单信号进行降噪&#xff0c;主要是降噪流程&#xff0c;为以后的小波更复杂的降噪算法打下良好的基础。降噪算法流程大致如下&#xff1a; &#xff08;1&#xff09;去趋势项&#xff08;如直流电流&#xff09;&#xff0c;并将数据归一…

词向量模型评估

一、既有范式 词向量的语言学特性&#xff1a;这部分主要通过一些具体的指标来评估词向量是否能捕捉到语言的内在规律&#xff0c;包括&#xff1a; 相似度评价指标&#xff1a;检查词向量空间中距离近的词是否与人类直觉一致&#xff0c;例如&#xff0c;利用余弦相似度来评估…

【嵌入式智能产品开发实战】(十三)—— 政安晨:通过ARM-Linux掌握基本技能【运行环境】

目录 简述 开始 操作系统环境下的程序运行 裸机环境下的程序运行 程序入口main()函数分析 BSS段的小提示 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 嵌入式智能产品开发实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不…

基于Java的高校成绩报送系统的设计与实现

基于Java的高校成绩报送系统的设计与实现 获取源码——》哔站搜&#xff1a;计算机专业毕设大全 获取源码——》哔站搜&#xff1a;计算机专业毕设大全

计算机基础入门7:大学计算机基础

第1章 计算机的基本概念 1.1 计算机概述 1、 电子计算机{电子模拟计算机&#xff0c;电子数字计算机} 2、 计算机之父——冯诺依曼(J. Von Neumann)&#xff0c;奠定现代计算机的体系结构。 3、 冯诺依曼在EDVAC设计方案中提出了“存储程序”原理 4、 计算机的三个特征&…

【Spring】SpringBoot整合Redis,用Redis实现限流(附Redis解压包)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 本文介绍SpringBoot整合Redis并且进行接口的限流&#xff0c;文章主要介绍的是一种思想&#xff0c;具体代码还要结合实际。 一、Windows安装Redis Redis的解压包我放在了百度网盘上&#xff0c;有需要的可以下载。 R…

java自动化测试-03-05java基础之字符串

1、字符串的定义 String是变量类型&#xff0c;表示字符串类型 name是给这个变量起的名字&#xff0c;这个是可以随意取的&#xff0c;只要不是java的关键字就可以了 表示赋值&#xff0c;右边的的内容表示 变量值&#xff0c;对字符串变量进行 赋值&#xff0c;需要用双引号…

C++模板实参推断

模板实参推断 我们已经看到&#xff0c;对于函数模板&#xff0c;编译器利用调用中的函数实参来确定其模板参数。 从函数实参来确定模板实参的过程被称为模板实参推断。 也就是说&#xff0c;只有函数参数才配有模板实参推断&#xff0c;函数返回类型是不配有的 在模板实参…

每日面经分享(python part1)

Python中的深拷贝和浅拷贝的区别是什么&#xff1f; a. 浅拷贝创建一个新的对象&#xff0c;但其中的可变元素仍然共享引用。只有对象的第一层被复制&#xff0c;而更深层次的嵌套对象仍然是引用。更改其中一个对象的属性会影响到其他对象。 b. 深拷贝创建一个完全独立的新对象…

营销中的归因人工智能

Attribution AI in marketing 归因人工智能作为智能服务的一部分&#xff0c;是一种多渠道算法归因服务&#xff0c;根据特定结果计算客户互动的影响和增量影响。有了归因人工智能&#xff0c;营销人员可以通过了解每个客户互动对客户旅程每个阶段的影响来衡量和优化营销和广告…

MT3017 上色

思路&#xff1a;使用分治&#xff0c;在每个连续区域递归调用heng()和shu() #include <bits/stdc.h> using namespace std; int n, m; int h[5005];int shu(int l, int r) {return r - l 1; } int heng(int l, int r) {int hmin 0x3f3f3f3f;for (int i l; i < r;…

银行数字化转型导师坚鹏:银行数字化转型给总行带来的9大价值

银行数字化转型给总行带来的9大价值 银行数字化转型对总行的深远影响是多方面的&#xff0c;银行数字化转型导师坚鹏从以下9个方面进行详细分析&#xff0c;相信能够给您带来重要价值。 1. 客户价值 银行数字化转型可以利用大数据、智能化风控模型为客户设计、提供“千人千面…

多模态系列-综述Video Understanding with Large Language Models: A Survey

本文是LLM系列文章,针对《Video Understanding with Large Language Models: A Survey》的翻译。 论文链接:https://arxiv.org/pdf/2312.17432v2.pdf 代码链接:https://github.com/yunlong10/Awesome-LLMs-for-Video-Understanding 大型语言模型下的视频理解研究综述 摘要…

Python学习笔记-Flask接收post请求数据并存储数据库

1.引包 from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy 2.配置连接,替换为自己的MySQL 数据库的实际用户名、密码和数据库名 app Flask(__name__) #创建应用实列 app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://ro…

Linux笔记之制作基于ubuntu20.4的最小OpenGL C++开发docker镜像

Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像 —— 2024-04-03 夜 code review! 文章目录 Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像1.这里把这本书的例程代码放在了Dockerfile所在的文件夹内以使镜像预装例程代码2.创建Dockerfile3.构建Do…