Go文档中频繁警告,不要无谓地使用包
unsafe
来破坏Go的类型系统。
unsafe.Pointer
因为具有不安全性而被明确标识。
包unsafe
包unsafe包含操作,可以绕过Go程序的类型安全检查。
导入unsafe包的软件包可能不可移植,并且不受Go 1兼容性指南的保护。
类型Pointer
因此,Pointer允许程序破坏类型系统并读取和写入任意内存。 应该非常小心地使用它。
命令cgo
传递指针
Go是一种垃圾收集语言,垃圾收集器需要知道每个指向Go内存的指针的位置。 因此,在Go和C之间传递指针有一些限制。
这些规则在运行时动态检查。
可以通过使用unsafe包来打破此强制执行,当然,C代码可以随意做任何事情。 但是,违反这些规则的程序可能会以意想不到且无法预测的方式失败。
让我们来看一个现实的例子。
这是一个接收字节缓冲区的C函数。
void printbuf(size_t len, unsigned char *buf)
在Go语言中,我们可以使用cgo并保持类型安全性,使用匹配的类型编写以下代码:
var buf []byte
C.printbuf(C.size_t(len(buf)), (*C.uchar)(&buf[0]))
然而,这仍然是不安全的,如果len(buf)== 0,则
buf [0]
将超出范围。 当
buf
初始化为其零值时,数组指针也将是
nil
。 我们可以在Go函数中整洁地封装完整性检查,Go GC优化编译器将内联该函数。
func cbuf(buf []byte) (size C.size_t, ptr *C.uchar) {
var bufptr *byte
if cap(buf) > 0 {
bufptr = &(buf[:1][0])
}
return C.size_t(len(buf)), (*C.uchar)(bufptr)
}
并且
bufsize, bufptr := cbuf(buf)
C.printbuf(bufsize, bufptr)
使用
unsafe.Pointer
来打破类型系统是不安全的。例如,
C.printbuf(C.size_t(len(buf)), (*C.uchar)(unsafe.Pointer(&buf[0])))
buf
类型可以是任何索引类型:数组、指向数组的指针、切片、字符串或映射。更糟糕的是,如果大小不是一个字节,那么大小将会错误。现在变得非常丑陋。
C.printbuf(C.size_t(len(buf)*int(unsafe.Sizeof(buf[0]))), (*C.uchar)(unsafe.Pointer(&buf[0])))
我们还没有考虑到空指针和超出范围的值。
接下来进行代码审查:代码应该正确、可维护、健壮、合理高效,最重要的是易读性好。不要期望 unsafe.Pointer
的使用能通过代码审查。
请说明你使用 unsafe.Pointer
的原因。
示例代码:
printbuf.go
:
package main
import "C"
import (
"unsafe"
)
func cbuf(buf []byte) (size C.size_t, ptr *C.uchar) {
var bufptr *byte
if cap(buf) > 0 {
bufptr = &(buf[:1][0])
}
return C.size_t(len(buf)), (*C.uchar)(bufptr)
}
func main() {
var buf []byte
bufsize, bufptr := cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0)
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0, 32)
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 32)
for i := range buf {
buf[i] = byte(i)
}
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
if len(buf) > 0 {
C.printbuf(C.size_t(len(buf)), (*C.uchar)(&buf[0]))
C.printbuf(C.size_t(len(buf)), (*C.uchar)(unsafe.Pointer(&buf[0])))
C.printbuf(C.size_t(len(buf)*int(unsafe.Sizeof(buf[0]))), (*C.uchar)(unsafe.Pointer(&buf[0])))
}
}
输出:
0 []
0 []
0 []
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
unsafe.Pointer
。如果你正在不安全地转换类型,那么你需要使用unsafe包来“删除”类型以完成转换。 - JimB*byte => *uint8 => *C.uchar
比*byte => *uint8 => *ArbitraryType => *C.uchar
更少有效的转换是如何发生的? - Diamondo25[]byte -> uint8_t*
进行转换,但您并没有这样做,您正在进行*byte -> *uint8
的转换,这是安全的。(是的,uint8
,byte
和C.uchar
都是相同的类型,这就是为什么转换成功的原因)。此外,请注意许多类型转换是“NOP”,因为除了为编译器更改声明的类型之外,您没有执行任何操作。(所有例外情况都在规范中列出:https://golang.org/ref/spec#Conversions) - JimB