Golang | C数组转化为Golang的切片类型
概述
最近在巩固cgo的基础知识,在网上看到一篇Go和C之间 字符串数组、切片类型转换的文章,让我想到我之前写的一篇在go中遍历C结构体数组的文章,让我有新的方法来解决之前的问题,把C的数组转化为Go的切片,对于文章的方法我直接”拿来主义“。
数组、字符串和切片
我们将一段特定长度的内存统称为数组。C语言的字符串是一个char类型的数组,字符串的长度需要根据表示结尾的NULL字符的位置确定。C语言中没有切片类型。
在Go语言中,数组是一种值类型,而且数组的长度是数组类型的一个部分。Go语言字符串对应一段长度确定的只读byte类型的内存。Go语言的切片则是一个简化版的动态数组。
Go语言和C语言的数组、字符串和切片之间的相互转换可以简化为Go语言的切片和C语言中指向一定长度内存的指针之间的转换。
以克隆的方式进行类型转换
CGO的C虚拟包提供了以下一组函数,用于Go语言和C语言之间数组和字符串的双向转换:
func C.CString(string) *C.char //go字符串转化为char*
func C.CBytes([]byte) unsafe.Pointer // go 切片转化为指针
func C.GoString(*C.char) string //C字符串 转化为 go字符串
func C.GoStringN(*C.char, C.int) string
func C.GoBytes(unsafe.Pointer, C.int) []byte
其中C.CString
针对输入的Go字符串,克隆一个C语言格式的字符串;返回的字符串由C语言的malloc
函数分配,不使用时需要通过C语言的free
函数释放。C.CBytes
函数的功能和C.CString
类似,用于从输入的Go语言字节切片克隆一个C语言版本的字节数组,同样返回的数组需要在合适的时候释放。C.GoString
用于将从NULL结尾的C语言字符串克隆一个Go语言字符串。C.GoStringN
是另一个字符数组克隆函数。C.GoBytes
用于从C语言数组,克隆一个Go语言字节切片。
克隆方式实现转换的优点是接口和内存管理都很简单,缺点是克隆需要分配新的内存和复制操作都会导致额外的开销。
上面粗体部分表示,利用C.CString
把go字符串转化为C字符串,内存由C语言的malloc
分配,不使用时需要free
释放内存,否则会出现内存泄漏。
通过直接访问C语言的内存来进行数据转换
在reflect
包中有字符串和切片的定义:在reflect
包中有字符串和切片的定义:
type StringHeader struct {<!-- -->
Data uintptr
Len int
}
type SliceHeader struct {<!-- -->
Data uintptr
Len int
Cap int
}
如果不希望单独分配内存,可以在Go语言中直接访问C语言的内存空间:
/*
#include <string.h>
char arr[10];
char *s = "Hello";
*/
import "C"
import (
"reflect"
"unsafe"
)
func main() {<!-- -->
// 通过 reflect.SliceHeader 转换
var arr0 []byte
var arr0Hdr = (*reflect.SliceHeader)(unsafe.Pointer(&arr0))
arr0Hdr.Data = uintptr(unsafe.Pointer(&C.arr[0]))
arr0Hdr.Len = 10
arr0Hdr.Cap = 10
// 通过切片语法转换
arr1 := (*[31]byte)(unsafe.Pointer(&C.arr[0]))[:10:10]
var s0 string
var s0Hdr = (*reflect.StringHeader)(unsafe.Pointer(&s0))
s0Hdr.Data = uintptr(unsafe.Pointer(C.s))
s0Hdr.Len = int(C.strlen(C.s))
sLen := int(C.strlen(C.s))
s1 := string((*[31]byte)(unsafe.Pointer(C.s))[:sLen:sLen])
}
因为Go语言的字符串是只读的,用户需要自己保证Go字符串在使用期间,底层对应的C字符串内容不会发生变化、内存不会被提前释放掉。
在CGO中,会为字符串和切片生成和上面结构对应的C语言版本的结构体:
typedef struct {<!-- --> const char *p; GoInt n; } GoString;
typedef struct {<!-- --> void *data; GoInt len; GoInt cap; } GoSlice;
在Go语言中直接访问C语言的内存空间的例子
package main
/*
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *name;
int age;
}person;
//一个长度为10的person结构体数组
person pon[10];
void NewPersonArray()
{
int n = 10;
//初始化name
for(int i = 0; i<n;i++){
pon[i].name = (char*)malloc(sizeof(char)*10);
pon[i].age = i;
sprintf(pon[i].name, "name:%d", i);
}
}
//释放内存
void freePersonArray()
{
for (int i = 0; i < 10; i ++){
free(pon[i].name);
}
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {<!-- -->
C.NewPersonArray()
//通过切片语法转换
arr1 := (*[20]C.person)(unsafe.Pointer(&C.pon[0]))[:10:10]
for _, v := range arr1 {<!-- -->
fmt.Printf("p.name: %s, p.age: %d\n", C.GoString(v.name), int(v.age) )
}
C.freePersonArray()
}
通过切片语法转换把C的结构体数组转换为go的数据
arr1 := (*[20]C.person)(unsafe.Pointer(&C.pon[0]))[:10:10]
注意,如果
C.pon
是一个数组指针该方法就不适用,在go中无法使用索引C.pon[0]
的 方法来访问C数组指针中的数据。比如有一个长度为10的数组指针 *C.pon,则在go中无法通过C.pon[0] 索引的方式来访问数据。如果有一个长度为10的数组[10]C.pon
, 则可以使用C.pon[0] 索引的方式来访问数据,不过在go中还需要转化为切片才能访问
输出:
p.name: name:0, p.age: 0
p.name: name:1, p.age: 1
p.name: name:2, p.age: 2
p.name: name:3, p.age: 3
p.name: name:4, p.age: 4
p.name: name:5, p.age: 5
p.name: name:6, p.age: 6
p.name: name:7, p.age: 7
p.name: name:8, p.age: 8
p.name: name:9, p.age: 9
给一个小彩蛋
在go中一个长度为10的char, 通过访问数组首位元素的地址来输出整个数组
package main
/*
#include <stdio.h>
void NewChar(char *s, int n)
{
sprintf(s, "I'm char");
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {<!-- -->
var c [10]C.char
C.NewChar(&c[0], C.int(10))
//通过数组首元素地址输出整个数组
arr := C.GoString(&c[0])
fmt.Printf("111111: %s\n",arr)
}
参考文章:https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-03-cgo-types.html
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2021/01/goslience/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接