Go 文档中充斥着警告不要使用包 unsafe 来不必要地破坏 Go 的类型系统。 unsafe.Pointer 被明确标识为不安全是有原因的。
Package unsafe
包不安全包含绕过类型安全的操作
围棋程序。
导入 unsafe 的包可能是不可移植且不受保护的
遵循 Go 1 兼容性指南。
type Pointer
因此,指针允许程序破坏类型系统并读取
并写入任意内存。使用时应格外小心。
Command cgo
Passing pointers
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])))
而且我们还没有考虑 nil 指针和超出范围的值。
接下来是代码审查:代码应该是正确的、可维护的、健壮的、相当高效的,并且最重要的是可读性。不要指望unsafe.Pointer 用法的代码审查进展顺利。
让我们听听您使用 unsafe.Pointer 的理由。
示例代码:
printbuf.go:
package main
/*
#include <stdio.h>
void printbuf(size_t len, unsigned char *buf) {
printf("%lu [", len);
if (!buf) {
len = 0;
}
size_t maxwidth = 16;
size_t width = len <= maxwidth ? len : maxwidth;
for (size_t i = 0; i < width; i++) {
if (i > 0) {
printf(" ");
}
printf("%02X", buf[i]);
}
if (width < len) {
printf(" ...");
}
printf("]\n");
}
*/
import "C"
import (
"unsafe"
)
// NOTE: -gcflags='-m' : can inline cbuf : inlining call to cbuf
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 // zero-value = nil, len = 0, cap = 0
bufsize, bufptr := cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0) // len = 0, cap = 0
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0, 32) // len = 0
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 32) // len > 0
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 ...]