理解golang函数传递slice

开始看golang的时候,最纠结的就是函数传递slice,这个slice到底有没有发生变化。最近发现周围的同事也没有弄清楚,于是打算仔细看下,在函数里修改传递的slice会不会改变原来的slice。

slice 结构

首先从slice的结构入手,可以看到slice底层其实就是一个数组

1
2
3
4
5
6
7
8
9
10
11
type slice struct {
array unsafe.Pointer
len int
cap int
}

type SliceHeader struct {
Data uintptr
Len int
Cap int
}

函数传递

需要明确的一点是,golang是值传递,copy value

  1. 传值

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"

func modify(B []int) {
for i := range s {
B[i]++:
}
}

func main() {
A := []int{1, 2, 3}
modify(A)
fmt.Println(A)
}

那么s究竟会不会改变呢?答案是,会改变的。问题来了,不是说golang函数传递是传值么?这时候不应该copy一份slice么?那为什么main函数里的slice会发生变化呢?

打印出来的结果是

1
[2, 3, 4]

接下里利用unsafe包直接对比下main函数和modify函数的slice对应的底层数组是不是一个,如果底层数组是一个,那显然在modify函数修改会影响main函数的slice。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import (
"fmt"
"reflect"
"unsafe"
)

func modify(B []int) {
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&B)).Data)
for i := range B {
B[i]++
}
}

func main() {
A := []int{1, 2, 3}
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&A)).Data)
modify(A)
fmt.Println(A)
}

打印出来的结果是

1
2
3
824634466336
824634466336
[2 3 4]

打印之后发现,指向的是同一个数组呀,那就验证了我们的猜测。其实传值的时候,相当于是这个样子的

main: A –> SliceHeader

Modify: B –> 同一个SliceHeader

例子2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func appendToS(s []int) []int {
s = append(s, 100)
return s
}

func main() {
s := []int{1, 2, 3}
newS := appendToS(s)
fmt.Println(s)
fmt.Println(newS)
}

看了例子1,你很开心的说,打印出来的s和newS是一样的,但我要告诉你猜错辽。你不由得感叹这是为啥子啊?明明指向一个数组,为什么没有变化呢?

打印结果如下

1
2
[1 2 3]
[1 2 3 100]

再利用unsafe包搞一把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"reflect"
"unsafe"
)

func appendToS(s []int) []int {
s = append(s, 100)
return s
}

func main() {
s := []int{1, 2, 3}

sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
fmt.Println(sptr.Len)

newS := appendToS(s)
fmt.Println(s)
fmt.Println(newS)

fmt.Println(sptr.Len)
fmt.Println(sptr.Cap)
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&newS)).Len)
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&newS)).Cap)
}

打印结果是

1
2
3
4
5
6
7
3
[1 2 3]
[1 2 3 100]
3
3
4
6

这个是因为原来的数组容量是3,append发现容量不够,触发了扩容,这时候底层数组就变掉了,如果容量充足的话,调大SliceHeader的长度是可以看到新增的元素。参考下runtime包slice.go的growslice函数。

  1. 传指针 无需多言,肯定会变化
Author: suikammd
Link: https://www.suikammd.com/2020/11/11/golong-slice-function/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.