【问题标题】:Runtime error with cgo and certain string slicescgo 和某些字符串切片的运行时错误
【发布时间】:2018-01-10 21:50:42
【问题描述】:

我已经解决了我在使用 swig 包装一些 C 代码以使用 golang 时遇到的问题,但问题并不在于 swig。

我可以传入一个基本字符串切片,但只要我用基本字符串以外的任何内容构造切片,我就会出现恐慌:运行时错误:cgo 参数具有指向 Go 指针的 Go 指针。

go version go1.8.5 linux/amd64

这是示例代码及其输出

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct { char *p; int n; } _gostring_;
typedef struct { void* array; int len; int cap; } _goslice_;

void prtText(char * const *txt, int len)
{
    int i = 0;

    for ( i=0; i<len; i++ ) {
        printf("Text %d is: %s\n", i, txt[i]);
    }
}

void _wrap_printText(_goslice_ _swig_go_0) {

  _gostring_ *p;

  char **arg1 = (char **)calloc(_swig_go_0.len, sizeof(char*));
  if (arg1) {
    for (int i=0; i<_swig_go_0.len; i++) {
      p = &(((_gostring_*)_swig_go_0.array)[i]);
      arg1[i] = calloc(1,(p->n)+1);
      strncpy(arg1[i], p->p, p->n);
    }
  }
  int arg2 = _swig_go_0.len;

  prtText((char *const *)arg1,arg2);
}

*/
import "C"

func PrintText(arg1 []string) {
    C._wrap_printText(*(*C._goslice_)(unsafe.Pointer(&arg1)))
}

func main() {
    s := []string{}

    s = append(s, "blah")
    s = append(s, "hello")
    s = append(s, "again")

    ns := []string{}

    ns = append(ns, "ns: "+s[0])
    ns = append(ns, "ns: "+s[1])
    ns = append(ns, "ns: "+s[2])

    fmt.Println("type s:", reflect.TypeOf(s))
    fmt.Println("type ns:", reflect.TypeOf(ns))
    fmt.Println("s:", s)
    fmt.Println("ns:", ns)

    PrintText(s)
    PrintText(ns)
}


go build -i -x -gcflags '-N -l' main.go

./main
type s: []string
type ns: []string
s: [blah hello again]
ns: [ns: blah ns: hello ns: again]
Text 0 is: blah
Text 1 is: hello
Text 2 is: again
panic: runtime error: cgo argument has Go pointer to Go pointer

如您所见,第一个字符串切片工作正常,但只要我执行基本字符串以外的任何操作,它就会失败。我尝试在将新字符串附加到切片之前先创建新字符串,但问题仍然存在。

我做错了什么?

【问题讨论】:

  • 您故意绕过任何安全检查。您需要先正确转换您的[]string。查看stackoverflow.com/questions/45997786/…的答案
  • 运行时出现恐慌,因为不允许将指针传递给 Go 分配的内存。虽然 GC 还没有移动内存,但它很容易收集一个超出范围的值,因为它被传递给 C 函数。

标签: go cgo


【解决方案1】:

您基本上是在传递原始的 Go 指针。 相反,您应该自己构建 C 数组。

一般来说,在几乎任何地方看到unsafe 都会让你产生怀疑。这很少是解决 cgo 问题的正确方法。

使用来自 Passing array of string as parameter from go to C function 的助手并在您的代码中使用它们:

package main

import (
  "fmt"
  "reflect"
)

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void prtText(char * const *txt, int len)
{
    int i = 0;

    for ( i=0; i<len; i++ ) {
        printf("Text %d is: %s\n", i, txt[i]);
    }
}

static char**makeCharArray(int size) {
        return calloc(sizeof(char*), size);
}

static void setArrayString(char **a, char *s, int n) {
        a[n] = s;
}

static void freeCharArray(char **a, int size) {
        int i;
        for (i = 0; i < size; i++)
                free(a[i]);
        free(a);
}

*/
import "C"

func main() {
  s := []string{}

  s = append(s, "blah")
  s = append(s, "hello")
  s = append(s, "again")

  ns := []string{}

  ns = append(ns, "ns: "+s[0])
  ns = append(ns, "ns: "+s[1])
  ns = append(ns, "ns: "+s[2])

  fmt.Println("type s:", reflect.TypeOf(s))
  fmt.Println("type ns:", reflect.TypeOf(ns))
  fmt.Println("s:", s)
  fmt.Println("ns:", ns)

  sargs := C.makeCharArray(C.int(len(s)))
  defer C.freeCharArray(sargs, C.int(len(s)))
  for i, p := range s {
    C.setArrayString(sargs, C.CString(p), C.int(i))
  }

  nsargs := C.makeCharArray(C.int(len(ns)))
  defer C.freeCharArray(nsargs, C.int(len(ns)))
  for i, p := range ns {
    C.setArrayString(nsargs, C.CString(p), C.int(i))
  }

  C.prtText(sargs, C.int(len(s)))
  C.prtText(nsargs, C.int(len(ns)))
}

现在的输出符合预期:

$ ./main 
type s: []string
type ns: []string
s: [blah hello again]
ns: [ns: blah ns: hello ns: again]
Text 0 is: blah
Text 1 is: hello
Text 2 is: again
Text 0 is: ns: blah
Text 1 is: ns: hello
Text 2 is: ns: again

【讨论】:

  • 问题是在使用 swig 时,它通过在 C 中使用相同的底层类型来处理原始的 go 指针。我真的不明白为什么在我给定的示例中使用 s 时这一切都有效并以 ns 失败。我了解您给定的解决方案,但现在需要将其用于 swig 绑定。
猜你喜欢
  • 1970-01-01
  • 2023-02-24
  • 2019-01-24
  • 2020-11-27
  • 2011-01-16
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多