指针字符串切片(*[]string)的Cgo指针传递规则?

Cgo pointer passing rule for pointer string slice (*[]string)?

我可以将 *[]string 从 Go 传递给 C,然后将 append 传递给字符串切片,还是违反了 pointer passing spec

Go code may pass a Go pointer to C, provided the Go memory to which it points does not contain any Go pointers.

示例代码:

package main

/*
extern void go_callback(void*, char*);

static inline void callback(void* stringSliceGoPointer) {
    go_callback(stringSliceGoPointer, "foobar");
}
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func main() {
    a := make([]string, 0)
    C.callback(unsafe.Pointer(&a)) 
    fmt.Println(a[0]) // outputs foobar
}

//export go_callback
func go_callback(stringSliceGoPointer unsafe.Pointer, msg *C.char) {
    slice := (*[]string)(stringSliceGoPointer)
    *slice = append(*slice, C.GoString(msg))
}

不,不可能。

参考this进一步解释go数据类型。

基本上 Go 中的字符串类型看起来像这样。

str := "hello"

这存储为,

 str:                0xad234e3b:
 ┌──────────┬─┐      ┌───┬───┬───┬───┬───┐
 |0xad234e3b|5|  ┌──>|104|101|108|108|111| -->[5]byte
 └────┬─────┴─┘  |   └───┴───┴───┴───┴───┘
      └──────────┘

考虑切片:

arr := string{"hi!","hello"}

进一步的Slice数据类型包含指针、长度、容量。

arr:                   0xd2b564c7:        0xad234e40:
┌──────────┬─┬─┐       ┌──────────┬─┐     ┌───┬───┬──┐
|0xd2b564c7|2|2|  ┌──> |0xad234e40|3|────>|104|105|33| -->[3]byte
└────┬─────┴─┴─┘  |    ├──────────┼─┤     └───┴───┴──┘
     └────────────┘    |0xad234e4b|5|──┐  0xad234e4b:
                       └──────────┴─┘  |  ┌───┬───┬───┬───┬───┐
                                       └─>|104|101|108|108|111| -->[5]byte
                                          └───┴───┴───┴───┴───┘

其中十六进制值代表地址。

实际存储数据的地方是[x]byte.

的数组

x表示数据(数组)的大小。

很明显[]string本身包含许多(x)个指针,而*[]string是一个额外的指针。

不允许将 *[]string 传递给 C,因为指向的内存包含字符串,而字符串包含指针。正如the cgo docs所说(强调我的)

Note that values of some Go types, other than the type's zero value, always include Go pointers. This is true of string, slice, interface, channel, map, and function types.

克服这个问题的一种方法是更间接地引用 []string,这样只有 Go 代码才真正知道它的地址。例如:

package main

/*
extern void go_callback(int, char*);

static inline void callback(int stringSliceRef) {
    go_callback(stringSliceRef, "foobar");
}
*/
import "C"

import (
    "fmt"
)

// If you need to use these values concurrently,
// you'll need more code to protect this.
var stringSlices = make([][]string, 10)

func main() {
    C.callback(0) 
    fmt.Println(stringSlices[0][0]) // outputs foobar
}

//export go_callback
func go_callback(ref C.int, msg *C.char) {
    i := int(ref)
    stringSlices[i] = append(stringSlices[i], C.GoString(msg))
}