golang切片
05切片
切片是引用,所以不需要额外的空间
切片组成元素:
- 指针:指向底层数组
- 长度:切片中元素的长度,不能大于容量
- 容量:指针所指向的底层数组的总容量
初始化方式
- 使用makeslice := make([]int, 5) // 初始化长度和容量都为 5 的切片 slice := make([]int, 5, 10) // 初始化长度为 5, 容量为 10 的切片
使用 make
关键字创建切片时,很多工作都需要运行时的参与;调用方必须在 make
函数中传入一个切片的大小以及可选的容量,cmd/compile/internal/gc.typecheck1
会对参数进行校验:
func typecheck1(n *Node, top int) (res *Node) {
switch n.Op {
...
case OMAKE:
args := n.List.Slice()
i := 1
switch t.Etype {
case TSLICE:
if i >= len(args) {
yyerror("missing len argument to make(%v)", t)
return n
}
l = args[i]
i++
var r *Node
if i < len(args) {
r = args[i]
}
...
if Isconst(l, CTINT) && r != nil && Isconst(r, CTINT) && l.Val().U.(*Mpint).Cmp(r.Val().U.(*Mpint)) > 0 {
yyerror("len larger than cap in make(%v)", t)
return n
}
n.Left = l
n.Right = r
n.Op = OMAKESLICE
}
...
}
}
上述函数不仅会检查 len
是否传入,还会保证传入的容量 cap
一定大于或者等于 len
,除了校验参数之外,当前函数会将 OMAKE
节点转换成 OMAKESLICE
,随后的中间代码生成阶段在 cmd/compile/internal/gc.walkexpr
函数中的 OMAKESLICE
分支依据两个重要条件对这里的 OMAKESLICE
进行转换:
- 切片的大小和容量是否足够小;
- 切片是否发生了逃逸,最终在堆上初始化
当切片发生逃逸或者非常大时,我们需要 runtime.makeslice
函数在堆上初始化,如果当前的切片不会发生逃逸并且切片非常小的时候,make([]int, 3, 4)
会被直接转换成如下所示的代码:
var arr [4]int
n := arr[:3]
上述代码会初始化数组并且直接通过下标 [:3]
来得到数组的切片,这两部分操作都会在编译阶段完成,编译器会在栈上或者静态存储区创建数组,[:3]
会被转换成上一节提到的 OpSliceMake
操作。
分析了主要由编译器处理的分支之后,我们回到用于创建切片的运行时函数 runtime.makeslice
,这个函数的实现非常简单:
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)
}
它的主要工作就是计算当前切片占用的内存空间并在堆上申请一片连续的内存,它使用如下的方式计算占用的内存:
内存空间 = 切片中元素大小 x 切片容量
虽然大多的错误都可以在编译期间被检查出来,但是在创建切片的过程中如果发生了以下错误就会直接导致程序触发运行时错误并崩溃:
- 内存空间的大小发生了溢出;
- 申请的内存大于最大可分配的内存;
- 传入的长度小于 0 或者长度大于容量;
runtime.makeslice
在最后调用的 runtime.mallocgc
是用于申请内存的函数,这个函数的实现还是比较复杂,如果遇到了比较小的对象会直接初始化在 Go 语言调度器里面的 P 结构中,而大于 32KB 的对象会在堆上初始化
- Python基础10 反过头来看看
- CCCF 微软沈向洋:理解自然语言:表述、对话和意境
- Linux进程基础
- Android Studio导入项目非常慢的解决办法
- Android开发中遇到的requestFeature() must be called before adding content异常
- Linux信号基础
- 剑指OFFER之合并有序链表(九度OJ1519)
- Android Studio快捷键每日一练(6)
- Linux文本流
- Linux并发与同步
- Android Studio快捷键每日一练(5)
- 2017年移动行业五大发展趋势及2018年前景展望(下)
- 剑指OFFER之反转链表(九度OJ1518)
- Android Studio快捷键每日一练(4)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法