go切片实现原理

近日一直在学习golang,已经产出如下博客一篇

  • GO闭包实现原理(汇编级讲解)

引言

最近在使用go语言的切片时,出现了一些意料之外的情况,遂查询相关文档学习后写下此篇博客

正文

首先,我们思考,go在通过函数传递一个切片时,是通过引用传递的吗,还是通过值传递的呢(答案将会很意外的哦)

值传递?

首先,先看如下简单代码,将一个string类型的切片传入函数后经过修改,在使用append()函数对切片进行添加之后,在函数的外部进行打印后却能发现,在内部添加数据并没有影响function()函数外面的str

看起来像是值传递,让我们继续往下看

func function(str []string){
	str = append(str,"c","lua","c#")
}


func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ java golang]

[Done] exited with code=0 in 1.586 seconds

引用传递?

将一个string类型的切片传入函数后经过修改,修改后影响到了外面[]string切片

func function(str []string){
	str[1] = "python"
}

func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ python golang]

所以,go的切片是使用的引用传递吗?

no,请继续向下看

我们可以惊奇的发现,先对切片进行append追加,在进行修改后,在函数外面进行打印,修改居然失效了

func function(str []string){
	str = append(str,"c","lua","c#")
	str[1] = "python"
}


func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ java golang]

但如果我们先用make()函数先对[]string切片的容量进行设置,在进行赋值又能发现是有效的

func function(str []string) {
	str = append(str, "c", "lua", "c#")
	str[1] = "python"
}

func main() {
	str := make([]string,3,10)
	str1 := []string{"c++", "java", "golang"}
	copy(str,str1)
	function(str)
	fmt.Print(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ python golang]
[Done] exited with code=0 in 1.858 seconds

到这里,读者的cpu是不是已经麻成一团了呢,哈哈,请先让我先对其中切片的原理进行讲解后,再回头来看,相信一定能看懂

原理解析

首先,切片类型在编译时期会生成一个结构体

  • array:相当于一个c语言的数组指针,指向切片的实际内存区域
  • len:切片的实际使用大小
  • cap:切片当前能够容纳的最大数量
type splice struct {
	array    unsafe.Pointer
	len      int
	cap		 int
}

如下图所示

而在我们将切片通过函数传入时候,go直接对这个结构体进行了一次拷贝,也就是说,拷贝的是这个结构体的值,而不是真正的数组,如下图所示,拷贝是一次浅拷贝,两个结构体指针指向同一个底层的数组

image-20231226211810026

所以,当我们没有使用make()函数生成切片类型,并且设置切片的cap容量时候:

go就会对底层的数组进行一次扩容,此时传入函数的切片的array就会指向一块新的内存,如下图所示,故修改无用

image-20231226211910073

然而,如果我们使用make()生成切片,并且设置了cap,那么就会发生如下事情

假设len==3,cap==10

  1. 切片传入函数后添加3条数据,此时函数内的切片len==6,cap==10
  2. 由于传入时候,传入的splice结构体是值传递,所以,函数外的splice结构体len==3,cap==10,也就是说len变量并没有被修改,但是对于[0,len]这个区间内的参数的修改是可见的,然而,由于go有着比较严格的内存安全检查,如果我们直接对[3,6]这个区间的内存进行访问,go会提示运行时错误

实测

接下来我们进行实测,通过一点类似于c语言指针的骚操作 ,绕过go的安全检查,验证我们理论的正确性

  1. 首先使用make()生成切片,设置len==3,cap==10
  2. 使用copy()函数,将切片前三个string变量进行赋值
  3. 将切片通过函数传递给function()函数,在function()函数内部进行追加以及修改
  4. function()函数返回后,通过类似于c语言指针的骚操作,绕过go的安全检查,访问到len[3:6]这个区间的内容
  5. 通过打印可以看见,如我们所想,在function()函数内的修改和添加都成功了
func function(str []string) {
	str = append(str, "c", "lua", "c#")
	str[1] = "python"
}

func main() {
	str := make([]string,3,10)
	str1 := []string{"c++", "java", "golang"}
	copy(str,str1)
	function(str)
	//fmt.Print(str)

    for i := 0; i < 6; i++ {
        ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&str[0])) + uintptr(i)*unsafe.Sizeof(str[0])) 
        fmt.Printf("%s,", *(*string)(unsafe.Pointer(ptr)))
    }
}
[Running] go run "d:\goProject\src\learn\package main.go"
c++,python,golang,c,lua,c#,
[Done] exited with code=0 in 1.757 seconds

总结

  • 切片在底层是一个结构体,在进行赋值传递时候,是将该结构体进行浅拷贝
  • 切片就是相当于一个动态数组,容量足够时候直接添加,不够时候重新创建一个更大的数组,再将原本的数据移动到新的数组(经过个人测试:默认二倍扩容,大小超过512时候不在使用二倍扩容,转而使用其他算法)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Axure Cloud如何给每个原型配置私有域名

需求 在原型发布之后&#xff0c;自动给原型生成一个独立访问的域名&#xff0c;类似http://u591bi.axshare.bushrose.cn&#xff0c;应该如何配置呢&#xff1f; 准备事项 已备案域名 如何备案&#xff1f;阿里云备案流程 已安装部署Axure Cloud 如何安装部署&#xff0c;请…

Redis系列之持久化机制RDB和AOF

Redis系列之持久化机制RDB和AOF 文章目录 1. 为什么需要持久化&#xff1f;2. 持久化的方式3. RDB机制3.1 RDB机制介绍3.2 配置RDB3.3 什么时候触发3.4 操作实例3.5 RDB优势和不足 4. AOF机制4.1 什么是AOF机制&#xff1f;4.2 同步机制4.3 重写机制4.4 AOF的优势和不足 混合模…

SQL注入攻击 - update注入

环境准备&#xff1a;构建完善的安全渗透测试环境&#xff1a;推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、MySQL UPDATE语句复习&#xff1a; UPDATE语句用于修改表中的数据&#xff0c;基本形式为&#xff1a;UPDATE 表名称 SET 列名称新值 WHERE 更新条件;语…

SpringCloud Ribbon 负载均衡服务调用

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第三篇&#xff0c;即介绍 Ribbon 负载均衡服务调用 二、概述 2.1 Ribbon 是什么 Spring Cloud Ribbon…

云计算 3月8号 (wordpress的搭建)

项目wordpress 实验目的&#xff1a; 熟悉yum和编译安装操作 锻炼关联性思维&#xff0c;便于以后做项目 nginx 编译安装 1、安装源码包 [rootlinux-server ~]# yum -y install gcc make zlib-devel pcre pcre-devel openssl-devel [rootlinux-server ~]# wget http://nginx.…

PCL不同格式点云读取速度(Binary和ASCII )

首先说明一点&#xff1a;Binary(二进制)格式点云文件进行读取时要比Ascll码格式点云读取时要快的多&#xff0c;尤其是对于大型的点云文件&#xff0c;如几百万、甚至几千万个点云的情况下。 今天遇到了一种情况&#xff0c;在写项目的时候进行点云读取&#xff0c;读取的时候…

3.5作业

课程代码复习&#xff1a; 使用select完成TCP并发服务器&#xff1a; #include<myhead.h> #define SER_IP "192.168.244.140" //服务器IP #define SER_PORT 8888 //服务器端口号int main(int argc, const char *argv[]) {//1、创建用于监听的套接…

6. 虚拟机及Linux安装

虚拟机及Linux安装 进行嵌入式项目开发&#xff0c;第一步就是要建立嵌入式开发环境&#xff0c;主要包括安装 Bootloader 工具、不同平台的交叉编译器&#xff08;如ARM 平台的arm-linux-gcc&#xff09;、内核源码树&#xff08;在需要编译和配置内核时&#xff09;、在调试…

docker学习进阶

一、dockerfile解析 官方文档&#xff1a; Dockerfile reference | Docker Docs 1.1、dockfile是什么&#xff1f; dockerfile是用来构建docker镜像的文本文件&#xff0c;由一条条构建镜像所需的指令和参数构成的脚本。 之前我们介绍过通过具体容器反射构建镜像(docker comm…

第11周,第三期技术动态

大家好&#xff0c;才是真的好。 真没想到&#xff0c;本周是今年第十一周&#xff0c;2024年还有不到三百天就结束了。 今天周五&#xff0c;我们继续介绍与Domino相关产品新闻&#xff0c;以及互联网或其他IT行业动态等。 一、在Windows 10和Windows 11上运行Domino和Trav…

错误和异常之标准异常创建异常

标准异常 表 10.2 列出了所有的 Python 当前的标准异常集,所有的异常都是内建的. 所以它们在脚本启动 前或在互交命令行提示符出现时已经是可用的了. 表10.2 Python内建异常 异常名称描述所有异常的基类 python 解释器请求退出 用户中断执行(通常是输入^C) 常规错误的基类

大模型时代下的自动驾驶研发测试工具链-SimCycle

前言&#xff1a; 最近OpenAI公司的新产品Sora的发布&#xff0c;正式掀起了AI在视频创作相关行业的革新浪潮&#xff0c;AI不再仅限于文本、语音和图像&#xff0c;而直接可以完成视频的生成&#xff0c;这是AI发展历程中的又一座重要的里程碑。AI正在不断席卷着过去与我们息…

仿牛客项目Day02:http、调试、日志、git

http状态码 后端调试 f8&#xff1a;逐行执行 f7&#xff1a;进入语句内部 f9&#xff1a;执行到下一个断点 前端调试 f10&#xff1a;逐行调试 f11&#xff1a;进入语句内部 f8&#xff1a;执行到下一个断点 日志 按照级别开启日志 日志的测试类 比如把application里…

基于交叉表生成风控规则(Python)

大家好&#xff0c;我是东哥。 规则是风控策略中最常用的工具之一&#xff0c;生成、筛选、监控、调优&#xff0c;几乎每天都在打交道&#xff0c;本篇来介绍如何基于交叉表来生成风控规则&#xff0c;并且如何基于评估指标进行筛选。 出品人&#xff1a;东哥起飞 专栏&#…

【字符串】【分类讨论】【KMP】1163. 按字典序排在最后的子串

作者推荐 视频算法专题 本文涉及知识点 字符串 字典序 分类讨论 本题无法使用KMP&#xff0c;因为t1不段变化。 LeetCode1163. 按字典序排在最后的子串 给你一个字符串 s &#xff0c;找出它的所有子串并按字典序排列&#xff0c;返回排在最后的那个子串。 示例 1&#xf…

图论入门题题解

✨欢迎来到脑子不好的小菜鸟的文章✨ &#x1f388;创作不易&#xff0c;麻烦点点赞哦&#x1f388; 所属专栏&#xff1a;刷题_脑子不好的小菜鸟的博客-CSDN博客 我的主页&#xff1a;脑子不好的小菜鸟 文章特点&#xff1a;关键点和步骤讲解放在 代码相应位置 拓扑排序 / 家谱…

基于Docker搭建Maven私服仓库(Linux)详细教程

文章目录 1. 下载镜像并启动容器2. 配置Nexus3. 配置本地Maven仓库 1. 下载镜像并启动容器 下载Nexus3镜像 docker pull sonatype/nexus3查看Nexus3镜像是否下载成功 docker images创建Nexus3的挂载文件夹 mkdir /usr/local/nexus-data && chown -R 200 /usr/local…

cadence 之 Allegro PCB封装 3D模型

Allegro PCB封装怎样赋3D模型 1、方式一 —— 设置器件高度 2、方式二 —— 指定STEP模型 2.1、Step 3D模型库 2.2、软件环境的设置和 STEP 模型库路径设置 D:\Cadence\Cadence_SPB_17.4-2019\share\local\pcb\step 2.3、指定STEP模型 即可打开 STEP 模型指定的对话框&…

【HarmonyOS】ArkTS-对象方法

目录 对象方法实例 对象方法 方法作用&#xff1a;描述对象的具体行为 约定方法类型 interface 接口名称 { 方法名: (参数:类型) > 返回值类型 }interface Person{dance: () > voidsing: (song: string) > void}添加方法&#xff08;箭头函数&#xff09; let ym: P…

服务器配置禁止IP直接访问,只允许域名访问

联网信息系统需设置只允许通过域名访问&#xff0c;禁止使用IP地址直接访问&#xff0c;建议同时采用云防护技术隐藏系统真实IP地址且只允许云防护节点IP访问服务器&#xff0c;提升网络安全防护能力。 一、Nginx 修改配置文件nginx.conf&#xff0c;在server段里插入正则表达式…