【问题标题】:(*C.uchar)(&buffer[0]) vs (*C.uchar)(unsafe.Pointer(&buffer[0]))(*C.uchar)(&buffer[0]) 与 (*C.uchar)(unsafe.Pointer(&buffer[0]))
【发布时间】:2018-07-17 14:42:11
【问题描述】:

我们在这里讨论了使用(或不使用)unsafe.Pointer 将指向字节数组的指针从 Go 传递到 C。

使用unsafe.Pointer() 的最大原因(不)是什么?我会以一致性为理由,因为您会调用“外部”函数,即使使用不同的语言,并且您希望保证它是指针类型。

但是,由于 Go 语言风格看起来有点像 C,使用 (*C.uchar)(&buffer[0])) 的直接转换是有效的并且 有效。它的工作原理并不让我相信它比使用unsafe.Pointer()安全

也许我对看起来像函数调用的 Go 强制转换有点困惑/冲突,而 Pointer 被定义为 type Pointer *ArbitraryType 实际上(*ArbitraryType)&buffer[0] 而确实 不是 实际上调用任何命令来进行“转换”,但实际上只是帮助解释正在发生的事情,在功能层面上,有点像宏可以

【问题讨论】:

  • 您在需要时使用unsafe.Pointer。如果您在类型之间进行不安全的转换,那么您将需要使用 unsafe 包“删除”类型以完成转换。
  • “它有效的事实并不能让我确信它更安全”——为什么?它更安全,因为它是有效的类型转换。尝试使用其他无法转换的类型,看看有什么不同。
  • 那么*byte => *uint8 => *C.uchar 的有效转化率究竟比*byte => *uint8 => *ArbitraryType => *C.uchar 少多少?
  • 我不明白你的意思。如果您更改类型(例如更改为 uint32)而不使用 unsafe,您将收到类似 cannot convert &buf[0] (type *uint32) to type *_Ctype_uchar 的错误
  • 对不起,我还没关注你。您无法转换[]byte -> uint8_t* ,但您没有这样做,您正在转换*byte -> *uint8,这是安全的。 (是的,uint8byteC.uchar 都是同一类型,这就是转换成功的原因)。另请注意,许多类型转换都是“NOP”,因为除了更改编译器的声明类型之外,您没有执行任何操作。 (例外情况均在规范中列出:golang.org/ref/spec#Conversions

标签: c go unsafe-pointers


【解决方案1】:

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) == 0buf[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 ...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-17
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    相关资源
    最近更新 更多