指针和内存互访
指针是c语言的灵魂,同样指针在go语言中也有很重要的位置。在cgo中go的指针和c的指针可以互相传递,但大多时候这种传递是危险的。go语言是一种有GC的语言,你很难知道指针指向的内容是否已经被回收。而c语言的指针通常情况下是稳定的(除非手动释放,或者返回局部变量指针)。本小节我们来探讨下cgo中指针的运用,以及cgo中go和c内存互相访问。
指针
在本节中,术语Go指针表示指向Go分配的内存的指针(例如通过使用&运算符或调用go内置函数new
),
术语C指针表示指向C分配的内存的指针(例如通过调用C.malloc
)。
指针是指向内存的起始地址,以指针类型是无关的。在c语言中我们使用void*
类型无关的指针,而在go是unsafe.Pointer
。这两者指针在cgo中是可以互相传递的。
c | go |
---|---|
void* | unsafe.Pointer |
go访问c的内存
go的由于内存策略的原因,很多时候向系统申请的内存在使用完之后,不会立即返回给系统,而是留着给下次使用。如果有场景想立即返回给出给系统,可以用cgo实现。
package main
/*
#include <stdlib.h>
void* m_malloc(int typeSize ,size_t length) {
return malloc(typeSize * length);
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
c := (*[3]int32)(C.m_malloc(C.int(4),3))
defer C.free(unsafe.Pointer(c))
c[0],c[1],c[2] = 0,1,2
fmt.Println(c,len(c))
}
$ go run main.go
&[0 1 2] 3
c访问go内存
我们cgo入门介绍了cgo字符串和字节数组的转换,用了C.CString
的方法将 go string转成 * C.char
。我们同时强调了在使用完* C.char
后记得调用C.free
方法。我们来看下C.CString
的定义
const cStringDef = `
func _Cfunc_CString(s string) *_Ctype_char {
p := _cgo_cmalloc(uint64(len(s)+1))
pp := (*[1<<30]byte)(p)
copy(pp[:], s)
pp[len(s)] = 0
return (*_Ctype_char)(p)
}
`
可以看出C.CString
是使用cgo函数_cgo_cmalloc
申请了内存,然后把go string数据拷贝到这块内存上。所以这块内存在c函数使用是安全的。但是很明显,耗性能,浪费内存空间。
cgo在调用c函数时,能保证作为参数传入c函数的go内存不会发生移动。所以我们可以使用如下方法让c函数临时访问go内存。
package main
/*
#include <stdio.h>
#include <stdlib.h>
int mconcat(const char * a,const char *b,char *c){
return sprintf(c,"%s%s",a,b);
}
*/
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
a := C.CString("hello")
b := C.CString("world")
defer func() {
C.free(unsafe.Pointer(a)) //需要手动释放
C.free(unsafe.Pointer(b)) //需要手动释放
}()
ret := make([]byte,20)
p := (*reflect.SliceHeader)(unsafe.Pointer(&ret))
len := C.mconcat(a,b,(*C.char)(unsafe.Pointer(p.Data)))
fmt.Println(len,string(ret[:len]))
}
$ go run main.go
10 helloworld
上面的例子中我们使用了go语言的[]byte
去接收c函数字符串拼接,这样子省去很多额外的内存分配。