我们在这里讨论了如何使用(或不使用) unsafe.Pointer将指针从Go传递到C。
使用unsafe.Pointer()的最大原因(不)是什么?我会把一致性作为一个理由,因为即使在另一种语言中,您也会调用“外部”函数,并且您希望保证它是一个指针类型。
但是,由于Go语言风格看起来有点像C,所以使用(*C.uchar)(&buffer[0]))直接转换是有效的,而且可以工作。它的工作原理并不能让我确信它比使用unsafe.Pointer()更安全。
也许我有点困惑/冲突,因为Go转换看起来像函数调用,而Pointer被定义为type Pointer *ArbitraryType实际上表示(*ArbitraryType)&buffer[0],实际上没有调用任何命令来执行“转换”,但实际上只是帮助解释在函数级别上正在发生的事情,就像宏可以做的那样。
发布于 2018-07-19 17:37:20
Go文档中到处都是关于使用包unsafe来不必要地击败Go类型系统的警告。unsafe.Pointer显然是不安全的,这是有原因的。
包装不安全 包不安全包含绕过Go程序类型安全性的操作。 导入不安全的包可能是不可移植的,并且不受Go 1兼容性准则的保护。 类型指针 因此,指针允许程序击败类型系统并读取和写入任意内存。使用时应格外小心。
Go是一种垃圾收集语言,垃圾收集器需要知道每个指针的位置才能进入内存。因此,在Go和C之间传递指针是有限制的。
这些规则在运行时被动态检查。
可以通过使用不安全的包来挫败这种强制执行,当然,没有什么可以阻止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]))然而,这仍然是不安全的,buf[0]将超出len(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
/*
#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 ...]https://stackoverflow.com/questions/51384245
复制相似问题