cmd/cgo: C函数返回void时错误地返回Go值

wmomyfyw  于 4个月前  发布在  Go
关注(0)|答案(9)|浏览(181)

尽管涉及类型为 C.void 的值的错误数量众多,但 cgo 仍未能拒绝以下程序。(“void”一词本身意味着“空”,因此对于类型“void”来说,包含一个值是毫无意义的!)

我不确定在多大程度上可以解决这个问题,考虑到修复 #3729 的方法添加了一个测试,以验证 Go 调用者可以将 _, err 绑定到对返回 void 的函数的调用结果。

package p

/*
static void return_void() { return; }
*/
import "C"

func F() {
	x := C.return_void() // ERROR HERE
	var y C.void = x     // ERROR HERE
	var z *C.void = &y   // ERROR HERE
	var b [0]byte = *z   // ERROR HERE
	_ = b
}
58wvjzkj

58wvjzkj1#

https://golang.org/cl/63830提到了这个问题:cmd/cgo: use a named type to indicate syntactic context

1zmg4dgp

1zmg4dgp2#

https://golang.org/cl/63831提到了这个问题:cmd/cgo: do not instantiate C.void

ct3nt3jp

ct3nt3jp3#

以下是一些关于这个问题的更多示例:

  • 我们(混淆地)在 C.void 是一个返回类型时将其Map到一个值,但在它是一个参数类型时却没有这样做:
//…

package p

/*
static void return_void() { return; }
static void consume_void(void) { return; }
*/
import "C"

func F() {
	impossible := C.return_void()  // Today: no error here.
	C.consume_void(impossible)
}

今天的错误:

src/issue21878d.go:15: too many arguments in call to _Cfunc_consume_void
	have (C.void)
	want ()
  • 我们报告了一个错误,即 unsafe.Pointer*C.void 不匹配,尽管我们将返回 void* 的函数Map为返回 unsafe.Pointer
//…
package p

/*
static void return_void() { return; }
static void consume_void_ptr(void* unused) { return; }
*/
import "C"

func F() {
	impossible := C.return_void()  // Today: no error here.
	C.consume_void_ptr(&impossible)
}

今天的错误:

# command-line-arguments
src/issue21878e.go:15: cannot use &impossible (type *C.void) as type unsafe.Pointer in argument to func literal
  • 另一方面,如果我们在 cgo 源代码中允许 *C.void,这个程序应该是正确的:
package cgotest

/*
static void* return_void_ptr() { return 0; }
static void consume_void_ptr(void* unused) { return; }
*/
import "C"

func testVoidPtrMustCompile() {
	var p *C.void = C.return_void_ptr()
	C.consume_void_ptr(p)
}

但今天它无法编译:

./issue21818.go:14: cannot use _Cfunc_return_void_ptr() (type unsafe.Pointer) as type *C.void in assignment
./issue21818.go:15: cannot use p (type *C.void) as type unsafe.Pointer in argument to func literal
f45qwnt8

f45qwnt84#

问题的根本在于,我们将 C.void 当作一个完整的、可示例化的类型来对待,但实际上并非如此。这意味着我们目前将 void* 翻译成两种不同的 Go 类型:当 void* 出现在 C 代码中时,我们将其翻译为 unsafe.Pointer ,但当它出现在 Go 代码中时,我们将其翻译为 *_Ctype_void
@hirochachacha 指出,我们可以通过 cgo 命令在 *C.void 每次出现时重写为 unsafe.Pointer ,类似于我为 #19487 中的其他不完整类型所做的建议,但如果我们不 消除 C.void 本身,我们仍然会为上面第二个示例产生令人困惑的错误信息。

z4bn682m

z4bn682m5#

我迄今为止找到的唯一一个实际使用 *C.void 的代码是这个文件,作者是 @paulsmith:

[https://github.com/paulsmith/gogeos/blob/master/geos/cwrappers.go](https://github.com/paulsmith/gogeos/blob/master/geos/cwrappers.go)

它是一个机械生成的 Package 器,错误地 Package 的 *C.void 函数似乎实际上并没有被调用。(但我要指出的是,在 GitHub 上搜索 C.void 受到了极大的阻碍,因为 GitHub 坚持忽略查询字符串中的所有点。)

q5lcpyga

q5lcpyga6#

作为另一个参考点,字符串 C.void 不在 Go Corpus v0.01 的任何源代码中出现。

bcmills:~/go-corpus-0.01$ grep 'C\.int' -r . | wc -l
2082
bcmills:~/go-corpus-0.01$ grep 'C\.void' -r . | wc -l
0
d5vmydt9

d5vmydt97#

“void”这个词本身意味着“空”,所以类型“void”包含一个值是没有意义的!
问题的根本在于,我们把C.void当作一个完整的、可示例化的类型来对待,但实际上它并不是。
只是好奇,将void视为等同于Go语言中的struct{}(空结构体)是否正确?虽然空结构体不能包含任何值,但它仍然是一个真实的类型,可以被示例化和赋值。那么void与此不同吗?

lbsnaicq

lbsnaicq8#

虽然空结构体不能包含任何值,但它仍然是一个真实的类型,可以示例化和赋值。那么 voidvoid 有什么不同呢?
void 是不同的。请参阅C11规范的第6.3.2.2节:
“一个 void表达式 (具有 void 类型的表达式)的(不存在的)值不应以任何方式使用,且不应对此类表达式应用隐式或显式转换(除非转换为 void )。”
然而,请注意,Go类型 struct{} 没有对应的C类型:“当......结构体或联合体被定义为不包含命名成员、无匿名结构和无匿名联合时,行为是未定义的(6.7.2.1)。”
相反,Go类型 struct{} 恰好有一个值,表示为 struct{}{} 。它可以显式转换为具有 struct{} 作为其底层类型的其他类型,并且在存储在 interface{} 类型的变量中时,可以与 nil 区分开来。

e5njpo68

e5njpo689#

(但也参见#20275(评论):在Go中具有不可示例化的unsafe.Void类型可能会允许更好地 Package 带有不完整类型的尾随成员的C结构体。)

相关问题