指针类型
在Go语言中,函数参数都是传值的,因此在函数内部对形参的操作不会影响到实参。但是使用指针类型传递参数,可以改变实参的数值。尽管如此,Go语言对指针类型的使用仍然有一定的限制。
- 限制一:指针类型不能进行数值运算
func main() {
a := 1
p := &a
p++ // 报错
}
p的类型为*int,上述代码会报错:invalid operation: p++ (non-numeric type *int)
- 限制二:不同类型的指针不能相互转换
func main() {
var a int = 1
var b *float64 = *float64(&a) // 报错
}
a的类型为int,b的类型为*float64,在将&a类型转换为b时,上述代码会报错:cannot convert &a (value of type *int) to float64
- 限制三:不同类型的指针不能使用
==
或者!=
比较
只有在两个指针类型相同或者可以相互转换的情况下,才可以对两者进行比较。另外,指针可以通过==
和!=
直接和nil
作比较。
func main() {
var a int = 1
var b int = 2
var c float32 = 2
fmt.Println(&a == &b) // 可以比较
fmt.Println(&a == &c) // 不能比较
}
在比较&a和&c时,上述代码会报错:cannot compare &a == &c (mismatched types *int and *float32)
- 限制四:不同类型的指针变量不能相互赋值
只有在两个指针类型相同或者可以相互转换的情况下,才可以相互赋值。另外,指针可以直接赋值为nil
。
func main() {
var a int = 1
p1 := &a
var b float32 = 2
p2 := &b
p1 = p2 // 报错
}
p1的类型为*int
,p2的类型为*float32,p2赋值p1时,会报错:cannot use p2 (variable of type *float32) as *int value in assignment
unsafe.Pointer
unsafe.Pointer是特别定义的指针类型,它可以包含任意变量的地址。普通类型的指针和unsafe.Pointer类型可以相互转换。
func main() {
var a int = 1
p1 := &a
p2 := unsafe.Pointer(p1)
p1 = (*int)(p2)
fmt.Println(*p1)
fmt.Println(*p2)// error: invalid indirect of p2 (type unsafe.Pointer)
}
如上述代码所示,p1的类型为*int
,p2的类型为unsafe.Pointer,p1和p2可以相互转换。
但是不可以直接通过*p2来获取unsafe.Pointer指针指向的真实变量的值,因为此时并不知道变量的具体类型。
此外,unsafe.Pointer也不支持数值运算。如需要数值运算,可以将unsafe.Pointer指针转化为uintptr类型,然后再做数值运算。
uintptr
uintptr是无符号整数类型,可以进行数值计算。
unsafe.Pointer指针可以转换为uintptr类型,但是将uintptr转换为unsafe.Pointer时,可能会出现问题,因为转换后的地址不一定是有效的内存地址。
type Test struct {
a int
b int
}
func main() {
t := &Test{}
temp := uintptr(unsafe.Pointer(t)) + unsafe.Offsetof(t.b)
tempP := (*int)(unsafe.Pointer(temp)) // subtly incorrect!
*tempP = 3
}
上述代码就是有错误的。
有时候,垃圾回收器会移动一些变量以降低内存碎片等问题,这类垃圾回收器被称为移动GC。当一个变量被移动,所有保存该变量旧地址的指针都必须同时被更新为变量移动后的新地址。从GC的角度看,unsafe.Pointer是指向变量的指针,因此变量被移动时,对应的指针也必须被更新。但是uintptr类型的变量不是指针,只是数字,所以其值不应该,也不会被改变。
上述错误代码引入临时变量temp,当temp转换为tempP时,变量t可能已经被移动,此时tempP已经不是&t.b的地址,那么向tempP赋值就会导致问题。
还有一些类似原因导致的错误,例如:
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 错误!
这里并没有指针引用new新创建的变量,因此该语句执行完成之后,垃圾收集器有权马上回收其内存空间,所以返回的pT将是无效的地址。
如果要将unsafe.Pointer转换为uintptr,并在做完数值运算之后再转换为unsafe.Pointer,则需要避免出现临时变量,采用一条赋值语句完成操作。
type Test struct {
a int
b int
}
func main() {
t := &Test{}
tempP := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(t)) + unsafe.Offsetof(t.b)))
*tempP = 3
}