数据类型
指针类型
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:操作这个指针指向的变量
输出
例如操作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 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
|