go切片实现原理

news2025/6/20 1:03:38

近日一直在学习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时候不在使用二倍扩容,转而使用其他算法)

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

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

相关文章

PHAMB: 病毒数据分箱

Genome binning of viral entities from bulk metagenomics data | Nature Communications 安装 ### New dependencies *Recommended* conda install -c conda-forge mamba mamba create -n phamb python3.9 conda activate phamb mamba install -c conda-forge -c biocond…

【蓝牙协议栈】【经典蓝牙】【BLE蓝牙】蓝牙协议规范(HCI、L2CAP、SDP、RFOCMM)

目录 1. 蓝牙协议规范&#xff08;HCI、L2CAP、SDP、RFOCMM&#xff09; 1.1 主机控制接口协议 HCI 1.2 逻辑链路控制与适配协议 L2CAP 1.3 服务发现协议SDP 1.4 串口仿真协议 RFCOMM 1. 蓝牙协议规范&#xff08;HCI、L2CAP、SDP、RFOCMM&#xff09; 1.1 主机控制接口协…

ReactNative项目构建分析与思考之react-native-gradle-plugin

前一段时间由于业务需要&#xff0c;接触了下React Native相关的知识&#xff0c;以一个Android开发者的视角&#xff0c;对React Native 项目组织和构建流程有了一些粗浅的认识&#xff0c;同时也对RN混合开发项目如何搭建又了一点小小的思考。 RN环境搭建 RN文档提供了两种…

读已提交隔离级别下竟然有间隙锁

业务背景 广告主痛点的为进行一次全媒体联合投放&#xff0c;若投放10个媒体&#xff0c;需要制作和上传10个创意、50张不同尺寸和出血区要求的图片和视频素材、近100个元素&#xff0c;投放成本极高。这也是制约部分用户使用新产品投放的原因。 因此进行升级。以三个创意为例…

消息队列以及Kafka的使用

什么是消息队列 消息队列&#xff1a;一般我们会简称它为MQ(Message Queue)。其主要目的是通讯。 ps&#xff1a;消息队列是以日志的形式将数据顺序存储到磁盘当中。通常我们说从内存中IO读写数据的速度要快于从硬盘中IO读写的速度是对于随机的写入和读取。但是对于这种顺序存…

SpringBoot集成对象存储服务Minio

MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&#xff0c;从…

【鸿蒙开发】第十七章 Web组件(一)

1 Web概述 Web组件用于在应用程序中显示Web页面内容&#xff0c;为开发者提供页面加载、页面交互、页面调试等能力。 页面加载&#xff1a;Web组件提供基础的前端页面加载的能力&#xff0c;包括&#xff1a;加载网络页面、本地页面、html格式文本数据。 页面交互&#xff1a…

FPGA的配置状态字寄存器Status Register

目录 简介 状态字定义 Unknown Device/Many Unknow Devices 解决办法 一般原因 简介 Xilinx的FPGA有多种配置接口&#xff0c;如SPI&#xff0c;BPI&#xff0c;SeletMAP&#xff0c;Serial&#xff0c;JTAG等&#xff1b;如果从时钟发送者的角度分&#xff0c;还可以…

EndNote插入引文换行不顶格的解决方法

引文换行不顶格 下载下的endNote的文献换行不顶格&#xff0c;如链接中EndNote插入引文换行不顶格的解决方法所示&#xff0c;换行不顶格。 解决方法 打开EndNote&#xff0c;依次打开「Edit」→「Output Styles」→「Edit"“」→「Bibliography」→「Layout」。 以编辑…

探索Cglib:解析动态代理的神奇之处

文章目录 CGLIB介绍CGLIB使用示例CGLIB核心原理分析代理类分析代理方法分析 FastClass机制分析 CGLIB介绍 CGLIB(Code Generation Library)是一个开源项目&#xff01;是一个强大的&#xff0c;高性能&#xff0c;高质量的Code生成类库&#xff0c;它可以在运行期扩展Java类与…

MACBOOK PRO M2 MAX 安装Stable Diffusion及文生图实例

以前偶尔会使用Midjourney生成一些图片&#xff0c;现在使用的头像就是当时花钱在Midjourney上生成的。前段时间从某鱼上拍了一台性价比还不错的macbook&#xff0c;想着不如自己部署Stable Diffusion&#xff08;以下简称SD&#xff09;尝试一下。 网上有很多教程&#xff0c…

四个领域,企业官网依然无可替代。

2023-10-23 14:17贝格前端工场 企业官网在以下领域无可替代&#xff1a; 专业性强的领域&#xff1a;如金融、法律、医学等&#xff0c;这些领域专业性很强&#xff0c;需要权威、专业的官网来提供详细、准确的信息1。需要展示企业形象、实力的领域&#xff1a;如制造业、房地…

【2023最全kafka面试和答案】

2023最全kafka面试和答案 ​ 1.Kafka中的ISR(InSyncReplicate)、OSR(OutSyncReplicate)、AR(AllReplicate)代表什么&#xff1f; ISR : 速率和leader相差低于10秒的follower的集合OSR : 速率和leader相差大于10秒的followerAR : 所有分区的followerARISROSR 2.Kafka中的HW、L…

Flink 学习3 - 流处理API的基本转换算子 + 多流转换算子

流处理API-Transform 1、基本转换算子 map、flatMap、filter通常被统一称为基本转换算子&#xff08;简单转换算子&#xff09; DataStream 里没有 reduce 和 sum 这类聚合操作的方法&#xff0c;因为 Flink 设计中&#xff0c;所有数据必须先分组才能做聚合操作。 先 keyB…

Java设计模式:建造者模式之经典与流式的三种实现(四)

本文将深入探讨Java中建造者模式的两种实现方式&#xff1a;经典建造者与流式建造者。建造者模式是一种创建型设计模式&#xff0c;它允许你构建复杂对象的步骤分解&#xff0c;使得对象的创建过程更加清晰和灵活。我们将通过示例代码详细解释这两种实现方式&#xff0c;并分析…

图分割 Graph Partition 学习笔记1

文章目录 前言一、graph-partition是什么&#xff1f;二、具体分类三、graph-partition的意义参考链接 前言 最近在学习图论划分的方法&#xff0c;碰巧搜索到了这个算是对我而言全新的一个体系&#xff0c;在这里将逐步记载自己的学习资料和进度&#xff0c;希望和大家一起探讨…

《汇编语言》第3版(王爽)实验9

第9章 实验9 编程&#xff1a;在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 ‘welcome to masm!’ assume cs:code,ds:datadata segmentdb welcome to masm!,0 data endscode segmentstart:mov ax,data mov ds,ax ;ds指向data段mov ax,0B800H ;显存空间从B800H…

波奇学Linux: 信号捕捉

sigaction:修改信号对应的handler方法 act输入型参数&#xff0c;oldact输出型参数 void (*sa_handler) (int) //修改的自定义函数 sigset_t sa_mask // void handler(int signo) {cout<<"catch a signal, signal number: "<<signo<<endl; } int …

C#实现快速排序算法

C#实现快速排序算法 以下是C#中的快速排序算法实现示例&#xff1a; using System;class QuickSort {// 快速排序入口函数public static void Sort(int[] array){QuickSortRecursive(array, 0, array.Length - 1);}// 递归函数实现快速排序private static void QuickSortRecu…

记录一次排查负载均衡不能创建的排查过程

故障现象&#xff0c;某云上&#xff0c;运维同事在创建负载均衡的时候&#xff0c;发现可以创建资源&#xff0c;但是创建完之后&#xff0c;不显示对应的负载均衡。 创建负载均衡时候&#xff0c;按f12发现console有如下报错 后来请后端网络同事排查日志发现&#xff0c;是后…