(*C.uchar)(&buffer[0]) 与 (*C.uchar)(unsafe.Pointer(&buffer[0])) 的区别

3
我们正在讨论在Go和C之间传递指向字节数组的指针时是否应该使用unsafe.Pointer
不使用unsafe.Pointer()最大的原因是什么?我认为一致性是一个原因,因为你会调用一个“外部”函数,即使是在另一种语言中,你也希望保证它是一个指针类型。
然而,由于Go语言的风格看起来有点像C,使用直接转换(*C.uchar)(&buffer[0]))是有效的并且可行的。事实上,它可行并不能让我相信它比使用unsafe.Pointer()更安全。
也许我有点困惑/矛盾于Go强制转换看起来像函数调用,以及Pointer被定义为type Pointer *ArbitraryType实际上是说(*ArbitraryType)&buffer[0]并没有实际调用任何命令来进行“转换”,而只是帮助解释正在发生的事情,从功能层面上看有点像宏可以做到的。

当你需要时,可以使用unsafe.Pointer。如果你正在不安全地转换类型,那么你需要使用unsafe包来“删除”类型以完成转换。 - JimB
它能够工作并不意味着我有信心它更安全 - 为什么?因为这是有效的类型转换,所以更安全。尝试使用无法转换的不同类型,看看区别。 - JimB
那么,*byte => *uint8 => *C.uchar*byte => *uint8 => *ArbitraryType => *C.uchar 更少有效的转换是如何发生的? - Diamondo25
我不明白你的意思。如果你改变类型(例如改为uint32),而不使用unsafe,你会得到一个错误,如“无法将&buf [0](类型* uint32)转换为类型*_Ctype_uchar”。 - JimB
好的,是的,当您想将输入对象解释为常规指针,然后将其转换为其他内容时,就可以这样做。我想对于[]byte -> uint8_t*转换来说,这实际上只是一个NOP。 - Diamondo25
1
抱歉,我还没有明白您的意思。您不能将[]byte -> uint8_t*进行转换,但您并没有这样做,您正在进行*byte -> *uint8的转换,这是安全的。(是的,uint8byteC.uchar都是相同的类型,这就是为什么转换成功的原因)。此外,请注意许多类型转换是“NOP”,因为除了为编译器更改声明的类型之外,您没有执行任何操作。(所有例外情况都在规范中列出:https://golang.org/ref/spec#Conversions) - JimB
1个回答

4
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

/*
#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 ...]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接