Part1 切片类型的定义
reflect.SliceHeader
1
2
3
4
5
6
7
8
9
10
11
// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
上述定义还不够清楚,我们再看下面这个定义。
1
2
3
4
5
type slice struct {
array unsafe.Pointer
len int
cap int
}
lencap
Part2 打印切片的地址
s
1
2
3
4
5
6
7
8
9
10
11
12
13
import "fmt"
func mSlicePrint() {
s := []int{1, 2, 3}
fmt.Printf("s: %p\n", s)
fmt.Printf("&s: %p\n", &s)
fmt.Printf("&s[0]: %p\n", &s[0])
}
// s: 0xc000018240
// &s: 0xc00000c0e0
// &s[0]: 0xc000018240
s&s[0]&s
%p
1
2
3
4
5
6
7
Slice
%p address of 0th element in base 16 notation, with leading 0x
Pointer
%p base 16 notation, with leading 0x
The %b, %d, %o, %x and %X verbs also work with pointers,
formatting the value exactly as if it were an integer.
s&s[0]
&sPart 1
下面这段代码打印出了切片结构体的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "fmt"
type slice struct {
array unsafe.Pointer
len int
cap int
}
func mSlicePrint() {
s := []int{1, 2, 3}
sp := (*slice)(unsafe.Pointer(&s))
fmt.Printf("%v\n", sp)
fmt.Printf("%p\n", &sp)
}
// &{0xc000018240 3 3}
// 0xc00000e098
// 对比上段代码
// s: 0xc000018240
// &s: 0xc00000c0e0
// &s[0]: 0xc000018240
Part3 切片的一个坑
我们知道 Go 语言无论是传递基本类型、结构体还是指针,都会对传递的参数进行复制,也就是传值。
传值时,函数调用会复制参数,被调用方和调用方持有不相关的两份数据。
那假如我们把切片作为参数传递,能否修改切片中的数据呢?来看下面这段代码。
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
29
30
31
32
33
34
35
import (
"fmt"
"unsafe"
)
type slice struct {
array unsafe.Pointer
len int
cap int
}
func changeSlice(s []int) {
s[0] = 200
s = append(s, 100)
sp := (*slice)(unsafe.Pointer(&s))
fmt.Printf("[changeSlice] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
return
}
func mSliceParam() {
s := make([]int, 1, 10)
sp := (*slice)(unsafe.Pointer(&s))
fmt.Printf("[mSliceParam] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
changeSlice(s)
sp = (*slice)(unsafe.Pointer(&s))
fmt.Printf("[mSliceParam] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
return
}
// [mSliceParam] [切片数据][0], [数组地址]0xc00001c190, [切片地址]0xc00000c0e0, [切片结构]&{0xc00001c190 1 10}
// [changeSlice] [切片数据][200 100], [数组地址]0xc00001c190, [切片地址]0xc00000c160, [切片结构]&{0xc00001c190 2 10}
// [mSliceParam] [切片数据][200], [数组地址]0xc00001c190, [切片地址]0xc00000c0e0, [切片结构]&{0xc00001c190 1 10}
&s
s[0]
s
s[0]append
len
1
2
3
[mSliceParam] &{0xc00001c190 1 10}
[changeSlice] &{0xc00001c190 2 10}
[mSliceParam] &{0xc00001c190 1 10}
changeSliceappendlenmSliceParam
Part4 如何解决1
既然len无法传递出来,那难道在外层函数就无法获取到新增的值吗?
在阅读io库的源码时,我看到了如下的接口。
1
2
3
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader接口的Read方法,接受一个[]byte切片,并返回读取到的字节数。
同样的,只要我们把新的len返回,不就能获取到新增的值了吗?
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
29
30
31
32
import (
"fmt"
"unsafe"
)
func changeSliceFix(s []int) (n int) {
s[0] = 200
s = append(s, 100, 50)
sp := (*slice)(unsafe.Pointer(&s))
fmt.Printf("[changeSlice] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
return len(s)
}
func mSliceParamFix() {
s := make([]int, 1, 10)
sp := (*slice)(unsafe.Pointer(&s))
fmt.Printf("[mSliceParam] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
n := changeSliceFix(s)
sn := s[:n]
sp = (*slice)(unsafe.Pointer(&s))
fmt.Printf("[mSliceParam s] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", s, s, &s, sp)
spn := (*slice)(unsafe.Pointer(&sn))
fmt.Printf("[mSliceParam sn] [切片数据]%v, [数组地址]%p, [切片地址]%p, [切片结构]%v\n", sn, sn, &sn, spn)
}
// [mSliceParam] [切片数据][0], [数组地址]0xc00001c190, [切片地址]0xc00000c0e0, [切片结构]&{0xc00001c190 1 10}
// [changeSlice] [切片数据][200 100 50], [数组地址]0xc00001c190, [切片地址]0xc00000c160, [切片结构]&{0xc00001c190 3 10}
// [mSliceParam s] [切片数据][200], [数组地址]0xc00001c190, [切片地址]0xc00000c0e0, [切片结构]&{0xc00001c190 1 10}
// [mSliceParam sn] [切片数据][200 100 50], [数组地址]0xc00001c190, [切片地址]0xc00000c1e0, [切片结构]&{0xc00001c190 3 10}
但要注意的是,这里对s的修改不能超过其容量,否则会触发扩容,新增的值会放在新的内存地址,那在函数外是无论如何也读取不到了。
Part5 如何解决2 切片指针
1
2
3
4
func changeSlicePointer(s *[]int) {
*s = append(*s, 100)
return
}