Golang中的指针解析之unsafe包

数据类型

指针类型

1
2
type Pointer *ArbitraryType
type ArbitraryType int // 64位操作系统上,占8个字节
  • 任何类型的指针都可以转换为unsafe.Pointer类型的指针。
  • unsafe.Pointer类型指针可以转换为任意类型的指针。
1
2
3
4
5
6
s := "abcd"
fmt.Printf("%p\n", &s) // 0x14000110210
sp := unsafe.Pointer(&s)
fmt.Printf("%p\n", sp) // 0x14000110210
i := (*int)(sp)
fmt.Printf("%v %v %p\n", s, *i, i) // abcd 4362945962 0x14000110210

指针的本质是一个地址和类型,类型代表解析指针的方式。通过转换成unsafe.Pointer,绕过了GO的类型安全机制,所以是不安全的操作。

  • unsafe.Pointer可以转换成uintptr
  • uintptr可以转换成unsafe.Pointer

转换成uintptr之后,就可以进行计算。

1
2
3
4
5
sli := [3]int{1, 2, 3}
sp := &sli
u := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(sp)) + unsafe.Sizeof(sli[0])))
*u += 10
fmt.Println(sli)

解析:

  • unsafe.Pointer(sp):获取数组的指针
  • uintptr(unsafe.Pointer(sp)):数组指针的具体数字,可以进行计算操作
  • unsafe.Sizeof(sli[0]):数组第一个元素的字节长度
  • uintptr(unsafe.Pointer(sp)) + unsafe.Sizeof(sli[0]):数组首位指针值加上第一个元素的长度,就是第二个元素的指针的Pointer类型
  • (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(sp)) + unsafe.Sizeof(sli[0]))):转换成int类型指针
  • *u += 10:操作这个指针指向的变量

输出

1
[1 12 3]

例如操作slice

1
2
3
4
5
type slice struct {
array unsafe.Pointer // 8个字节
len int
cap int
}
1
2
3
4
5
6
7
s := make([]int, 8, 9) // slice的len和cap是8 9 
l := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))
*l += 10
fmt.Printf("l: %d,len: %d,cap: %d\n", *l, len(s), cap(s))
c := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)))
*c += 20
fmt.Printf("c: %d,len: %d,cap: %d\n", *c, len(s), cap(s))
  • uintptr(unsafe.Pointer(&s)):获取slice的地址
  • unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)):加8个字节长度,就是len字段
  • (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8))):len字段地址
  • unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)):加8个字节长度,是len,再加8个字节长度,是cap

输出

1
2
l: 18,len: 18,cap: 9
c: 29,len: 18,cap: 29

操作结构体的私有成员

1
2
3
4
5
6
7
u := User{
Name: "mitaka",
age: 18,
}
a := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&u)) + unsafe.Offsetof(u.age)))
*a += 10
fmt.Printf("u: %+v\n", u)

输出

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
Name string
age int
}

u := User{
Name: "mitaka",
age: 18,
}
a := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&u)) + unsafe.Offsetof(u.age)))
*a += 10
fmt.Printf("u: %+v\n", u)
  • unsafe.Offsetof(u.age)):字段偏移量的字节长度

输出

1
u: {Name:mitaka age:28}

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type User struct {
Name string
age int
language string
}

u := User{
Name: "mitaka",
age: 18,
language: "php",
}
a := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&u)) + unsafe.Sizeof(string(0)) + unsafe.Sizeof(int(0))))
*a = "golang"
fmt.Printf("u: %+v\n", u)
  • unsafe.Sizeof(string(0)):字符串的偏移量字节长度
  • unsafe.Sizeof(int(0)):int类型的偏移量字节长度

零拷贝

通过操作底层数据,可以实现类型转换而不依赖内存拷贝

切片和字符串底层数据结构,在src/reflect/value.go中

1
2
3
4
5
6
7
8
9
10
type SliceHeader struct {
Data uintptr
Len int
Cap int
}

type StringHeader struct {
Data uintptr
Len int
}

实现方式:

使用强制转换,将*string类型,强制转换成*[]byte类型

1
2
3
4
5
6
7
8
9
10
11
12
s := "abcdefghijk"
sli := []byte(s)
fmt.Printf("s: %p\n", &s)
fmt.Printf("sli: %p\n", sli)
sli1 := (*[]byte)((unsafe.Pointer)((*reflect.StringHeader)(unsafe.Pointer(&s))))
sli2 := (*[]byte)(unsafe.Pointer(&s))
fmt.Printf("sli1:%s %p\n", *sli1, sli1)
fmt.Printf("sli2:%s %p\n", *sli2, sli2)
s = "ABCDEF"
fmt.Printf("s: %p\n", &s)
fmt.Printf("s: %s,sli1: %s\n", s, *sli1)
fmt.Printf("s: %s,sli2: %s\n", s, *sli2)

输出:

1
2
3
4
5
6
7
s:   0x14000110210
sli: 0x1400012c010
sli1:abcdefghijk 0x14000110210
sli2:abcdefghijk 0x14000110210
s: 0x14000110210
s: ABCDEF,sli1: ABCDEF
s: ABCDEF,sli2: ABCDEF

或者使用下面这种方式:

1
2
3
4
5
6
7
8
9
10
s := "abcdefghijk"
fmt.Printf("s: %p\n", &s)

sliptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
sli := (*[]byte)(unsafe.Pointer(sliptr))
fmt.Printf("sli: %p %s\n", sli, *sli)
fmt.Printf("s: %d,sli: %d\n", uintptr(unsafe.Pointer(&s)), uintptr(unsafe.Pointer(sli)))
s = "ABCDEF"
fmt.Printf("s: %p,sli1: %p\n", &s, sli)
fmt.Printf("s: %s,sli1: %s\n", s, *sli)

输出

1
2
3
4
5
s:   0x14000110210
sli: 0x14000110210 abcdefghijk
s: 1374390649360,sli: 1374390649360
s: 0x14000110210,sli1: 0x14000110210
s: ABCDEF,sli1: ABCDEF