> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# go源码阅读 slice
- URL: https://blog.hacking.pub/2019/07/25/goyuan-ma-yue-du-slice/
- Published: 2019-07-25T15:40:35.000Z
- Updated: 2019-07-25T15:41:20.000Z
- Author: chainhelen
- Tags: go, go语言, golang, go源码, 源码剖析, gdb, slice, 切片

### slice

对于`slice`来说，其实源码部分并不多，主要看一下`make slice`、`growslice`、`slicecopy`和`copy`

###### 编译阶段

`make slice`语句跟`map`一样，在`compile`的`typecheck`阶段，将`MAKE`节点偷偷换成`OMAKESLICE`节点

关于`oppend`的代码实在`/usr/local/go/src/cmd/compile/internal/gc/ssa.go`的`func (s *state) append(n *Node, inplace bool) `，这个是编译时代码

关于`slice`的代码在`/usr/local/go/src/runtime/slice.go` ，这部分是运行时代码

###### type slice struct

```
type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}

```

`array`就是`slice`底层对应数组的内存地址，所有结构体的数据都是连续排列在这的，

`len`当前的`slice`数据大小

`cap`当前的`slice`数据容量

###### makelist

```
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 {
      // NOTE: Produce a 'len out of range' error instead of a
      // 'cap out of range' error when someone does make([]T, bignumber).
      // 'cap out of range' is true too, but since the cap is only being
      // supplied implicitly, saying len is clearer.
      // See golang.org/issue/4085.
      mem, overflow := math.MulUintptr(et.size, uintptr(len))
      if overflow || mem > maxAlloc || len < 0 {
         panicmakeslicelen()
      }
      panicmakeslicecap()
   }

   return mallocgc(mem, et, true)
}

```

这里面其实`len`和`cap`都是默认传入进来的，实际`make slice`只返回分配的数组内存地址，而`len`和`cap`还是结构体自己去记录的

###### growslice

```
func growslice(et *_type, old slice, cap int) slice {
    // 这个 cap是即将要分配cap大小，例如
    // 	s0 := make([]int, 2, 1)
	// s0 = append(s0, 5, 6, 7, 8, 9)
    // 那么这个时候生成调用代码growslice的参数cap就是 2+5=7
    // 使用gdb 在 append那一行断点再step，就能看到数据
	if cap < old.cap {
		panic(errorString("growslice: cap out of range"))
	}

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
    ... 

	return slice{p, old.len, newcap}
}

```

上面主要是增长方式，记得三种情况就行：

1. 如果发生`append`之后的`cap`是超过原来的一倍，那么按超过的最大`cap`作为新的`cap`；否则进行以下逻辑
2. 如果原来的`old cap`小于1024（注意这里不是内存，而是容量），那么扩容 1 倍（即原来的2倍）；否则
3. 扩容1.25倍（即原来的2.25倍）

###### slicecopy

```
func slicecopy(to, fm slice, width uintptr) int {
   ...

   size := uintptr(n) * width
   if size == 1 { // common case worth about 2x to do here
      // TODO: is this still worth it with new memmove impl?
      *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
   } else {
      memmove(to.array, fm.array, size)
   }
   return n
}

```

这里面的`memmove`跟机器硬件指令有关（优化），都是使用汇编完成的；

有意思的是如果需要`copy`的数据是 1 字节，是直接赋值这个字节

###### append

```
func (s *state) append(n *Node, inplace bool) 

```

这个里面`inplace`代表了两种使用`append`的方式

```
If inplace is false, process as expression "append(s, e1, e2, e3)"
If inplace is true, process as statement "s = append(s, e1, e2, e3)"

```

不过`inplace==false`在我现在的版本`go version go1.12.5 linux/amd64`会编译错误

编译器希望我们处理一下`append`的返回值

###### 函数参数

```
func f(s1 []int) {
    // s1[0] = 200
	s1 = append(s1, 5, 6, 7, 8, 9)

	fmt.Printf("f s1=%v\n", s1)
}

func main() {
	s0 := make([]int, 1, 2)
	s0[0] = 10

	f(s0)

	fmt.Printf("main s0=%v\n", s0)
}
// output
f s1=[10 5 6 7 8 9]
main s0=[10]

```

可以理解为，`f(s0 []int)`其实调用是长这样的`f(array *int, len int, cap int)`，如果在函数`f`修改`s1`切片中的某个值（例如上面的注释）是可以变动`main`中`s0`;

但是类似`append`这种（如果`cap`不够导致触发`growslice`例外），可能会导致返回新的`array`指针，就形成了局部变量，不会影响`main`中的`s0`

###### 数组

函数内部的数组（函数外部会保存在数据段），类似只有切片结构的`array`字段，只有一个指针而已（指向一块分配好的内存），所以不能改变大小

```
// main.go
  1 package main
  2 
  3 import (
  4     "fmt"
  5 )
  6 
  7 func main() {
  8     array := [3]int{1, 2, 3}
  9     slice := array[:2]
 10     fmt.Printf("main s0=%v %v\n", array, slice)
 11 }

go build -gcflags="all=-N -l" -o main main.go 
gdb main
断点下在 第 10 行，
(gdb) info locals
&array = 0xc000016540
slice = {array = 0xc000016540, len = 2, cap = 3}

```

#### 参考

> [slice 和 array](https://github.com/cch123/golang-notes/blob/master/slice.md?ref=blog.hacking.pub)