静态语言与动态脚本语言有意思的是,可以直接操作指针,移动指针操作变量。
我们都学过C++,当时也被指针操作所困扰。go确实不能直接操作指针,但是go提供了一个unsafe包可以这么样的操作。
package unsafe
type ArbitraryType int
type IntegerType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
//go1.17
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
- Sizeof 返回变量在内存中占用的字节数
- Offsetof 返回变量指定属性的偏移量,这个函数是接收一个struct类型变量的属性当作参数。
- Alignof 返回当前变量对于当前struct类型的偏移量,考虑内存对齐
- Add (1.17新加入)指针运算,加上一个偏移量
- Slice (1.17新加入)返回一个切片
这里面还提到2个类型,uintptr和unsafe.Pointer
他俩的区别就是:
- unsafe.Pointer仅仅是通用指针类型,用于转换不同类型指针;
- uintptr仅仅是用于指针运算的;
转化过程如下:
普通指针->unsafe.Pointer->uintptr进行加减运算->unsafe.Pointer->普通指针
那么我也尝试了用代码体验了一番:
package unsafe_cmd
import (
"fmt"
"reflect"
"unsafe"
)
type MyT struct {
i32 int32
i64 int64
}
func (myT *MyT) GetI32() {
fmt.Printf("value: %v", myT.i32)
}
func (myT *MyT) GetI64() {
fmt.Printf("value: %v", myT.i64)
}
func UnsafeMain() {
my := &MyT{}
i32 := (*int32)(unsafe.Pointer(my))
*i32 = 187
i64 := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(my)) + uintptr(4)))
i644 := (*int64)(unsafe.Add(unsafe.Pointer(my), 8))
fmt.Println(unsafe.Offsetof(my.i32))
fmt.Println(unsafe.Offsetof(my.i64))
fmt.Println(reflect.ValueOf(i644))
fmt.Println(reflect.ValueOf(&my.i32))
fmt.Println(reflect.ValueOf(&my.i64))
*i64 = 100000
my.GetI32()
fmt.Println()
my.GetI64()
fmt.Println()
*i644 = 100001
my.GetI64()
fmt.Println()
}
打印结果:
$ go run main.go
0
8
0x14000096018
0x14000096010
0x14000096018
value: 187
value: 0
value: 100001
我先定义了一个struct,里面两个变量,一个int32,一个int64。
我先初始化了对象为myT,然后通过pointer对象的指针,获取到第一个属性的指针,赋值为187
然后用第一个属性的指针做了偏移量相加的运算,这里我加了4,然后赋值100000,原以为可以打印出对应值。结果打印的是0。这里的主要原因是因为内存对其。
后来我用Offsetof再次确认了一下,果然+4是错的。
于是我又换成+8(看代码我用的1.17的新特性,Add方法和直接±是一样的),赋值成功。