Go和C语言操作
Go 有很强的 C 背景。 除了语法的继承之外,它的设计者和它的设计目标都与C语言有着千丝万缕的联系。 在Go和C语言的互操作性方面(),Go提供了强大的支持。 尤其是在Go中使用C时,甚至可以直接在Go源文件中编写C代码,这是其他语言无法比拟的。 以下是小编为大家带来的关于Go和C语言的操作知识。 欢迎阅读。
1.Go调用C代码的原理
这是一个简短的例子:
复制代码
主要的
// #
// #
/*
无效打印(字符* str){
("%s", 字符串);
*/
“C”
””
函数主() {
s := "你好,Cgo"
cs := C.(s)
C.print(cs)
C.free(.(cs))
复制代码
与“正常”Go 代码相比,上面的代码有几个“特殊”的地方:
1)开头注释中出现C file字样
2)C函数print在注释中定义
3)名为C的“包”
4)上述的C函数——print实际上是在main函数中调用的。
是的,这些就是Go源码中调用C代码的步骤。 可见我们可以直接在Go源码文件中编写C代码。
首先,Go源文件中的C代码需要用注释包裹起来,就像上面的头文件和打印函数定义一样;
其次,“C”语句是必须的,并且它与上面的C代码不能用空行隔开,必须紧密相连。 这里的“C”并不是包名,而是一个类似于命名空间的概念,或者可以理解为伪包。 C语言的所有语法元素都在这个伪包下;
最后,在访问C语法元素时,必须在其前面添加伪包前缀,例如上面代码中的C.uint和C.print、C.free等。
我们如何编译这个go源文件呢? 事实上,它与“正常”的Go源文件没有什么不同。 还是可以直接通过go build或者go run来编译执行。 但是在实际的编译过程中,go会调用一个叫做cgo的工具。 cgo会识别并读取Go源文件中的C元素,将其提取出来交给C编译器进行编译,最后与Go源代码编译后的目标文件结合起来。 链接成可执行程序。 这样,我们就不难理解为什么Go源文件中的C代码被注释包裹了。 这些特殊语法可以被Cgo识别和使用。
2. Go中使用的C语言类型
1.原生型
* 数字类型
在 Go 中,您可以通过以下方式访问 C 的原生数值类型:
复制代码
C. 字符,
C.schar(字符),
C.uchar(字符),
C.短,
C.(短),
C.int、C.uint(int)、
C.长,
C.ulong(长),
C.(长长),
C.(长长),
C.浮动,
C。
复制代码
Go 的数值类型与 C 中的 . 类型并不是一一对应的。因此,在使用其他类型的变量时,显式转换操作是必不可少的,例如 Go doc 中的示例:
复制代码
函数 () int {
int(C.())//C.long -> Go 的 int
func 种子(i int) {
C.(C.uint(i))//Go uint -> C uint
复制代码
* 指针类型
根据 Go 语法,原生数值类型的指针类型可以在前面加 *,例如 var p *C.int。 而void*比较特殊,用 表示。 在围棋中。 任何类型的指针值都可以转换为 .type,而 .type 值也可以转换为任何类型的指针值。 .也可以与此类型相互转换。 由于 的指针类型。 不能进行算术运算,转换后可以进行算术运算。
* 字符串类型
C语言中没有正式的字符串类型。 在C语言中,使用尾随''的字符数组来表示字符串; 在Go中,类型是原生类型,因此需要使用字符来在两种语言之间进行互操作。 字符串类型转换。
通过C.函数,我们可以将Go类型转换为C“字符串”类型,然后传递给C函数使用。 就像我们在本文开头的示例中使用的那样:
s := "你好,Cgo"
cs := C.(s)
C.print(cs)
然而这样转换后得到的C字符串cs无法被Go的gc管理。 我们必须手动释放cs占用的内存。 这就是例子中最后调用C.free来释放cs的原因。 C内部分配的内存不知道Go中的GC,所以记得释放它。
C. 可以将C字符串(*C.char)转换为Go类型,例如:
复制代码
// #
// #
// char *foo = "";
“C”
“FMMT”
函数主() {
……
fmt.("%s", C.(C.foo))
复制代码
*数组类型
C语言中的数组与Go语言中的数组有很大不同。 后者是值类型,而前者和C中的指针大多数情况下可以随意转换。 目前看来,两者之间还不能直接显式地进行转换,官方文档也没有解释。 但是我们可以通过编写转换函数将C数组转换为Go (由于Go中的数组是值类型并且其大小是静态的,因此转换为Slice更通用)。 以下是整数数组转换的示例。 :
复制代码
// int[] = {1, 2, 3, 4, 5, 6, 7};
func ( ., size int) ( []int) {
p := ()
对于我:=0; 我<大小; 我++ {
j := *(*int)(.(p))
= (, j)
p += .(j)
函数主() {
……
:= (.(&C.[0]), 7)
fmt.()
复制代码
执行结果输出:[1 2 3 4 5 6 7]
这里需要注意的是,Go编译器无法自动将C值转换为数组的地址,因此数组变量不能像C中使用数组一样直接传递给函数,而是传递第一个元素的地址该数组被传递给函数。
2. 定制类型
除了原生类型之外,我们还可以访问 C 中的自定义类型。
* 枚举(枚举)
复制代码
// 枚举颜色 {
// 红色的,
// 蓝色的,
//
// };
var e, f, g C. = C.RED, C.BLUE, C.
fmt.(e、f、g)
复制代码
输出:0 1 2
对于命名的C枚举类型,我们可以通过C来访问该类型。如果是匿名枚举,似乎只能访问它的字段。
* 结构()
复制代码
// {
// 字符 *id;
// int 年龄;
// };
id := C.("1247")
var C. = C.{id, 21}
fmt.(C.(.id))
fmt.(.年龄)
C.free(.(id))
复制代码
输出:
第1247章
21
与enum类似,我们可以通过C来访问C中定义的结构体类型。
* 联盟
这里我尝试使用与访问相同的方法来访问 C 联合:
复制代码
// #
// 联合栏 {
// 字符 c;
// int i;
//d;
// };
“C”
函数主() {
var b *C。 = 新(C.)
BC = 4
fmt.(b)
复制代码
然而编译时go报错:bc(type *[8]byte has no field or c)。 从错误信息来看,Go对待联合的方式与其他类型不同。 似乎将联合视为[N]字节,其中N是联合中最大字段的大小(四舍五入),因此我们可以如下处理C:
函数主() {
var b *C。 = 新(C.)
b[0] = 13
b[1] = 17
fmt.(b)
输出:&[13 17 0 0 0 0 0 0]
*
在Go中访问使用Go定义的别名类型时,访问方法与原始实际类型访问方法相同。 喜欢:
复制代码
// int myint;
var a C.myint = 5
fmt.(a)
// ;
var m C.
复制代码
从示例中可以看出,对于原生类型的别名,可以直接访问新类型名称。 对于复合类型别名,需要按照原复合类型的访问方法来访问新的别名。 例如,如果实际类型是,那么使用时必须添加前缀。
3.在Go中访问C变量和函数
事实上,在上面的例子中,我们已经演示了如何在Go中访问C变量和函数。 一般的方法是加上C前缀,特别是对于C标准库中的函数。 然而,虽然我们可以直接在Go源码文件中定义C变量和C函数,但从代码结构上来说,在Go源码中编写大量的C代码似乎并没有那么“专业”。 那么如何将C函数和变量定义从Go源码中分离出来,单独定义呢? 我们很容易想到以共享库的形式将C代码提供给Go源代码。
Cgo 提供了 #cgo 指令来指定 Go 源代码编译后将链接到哪些共享库。 让我们看一个例子:
复制代码
主要的
// #cgo : -L ./ -lfoo
// #
// #
// #“foo.h”
“C”
“FMMT”
函数主() {
fmt.(C.count)
C.foo()
复制代码
我们看到,在上面的例子中,#cgo指令用于告诉go编译器链接当前目录中的共享库。 C.count 变量和 C.foo 函数的定义都在共享库中。 让我们创建这个共享库:
// foo.h
复制代码
整数计数;
无效 foo();
//foo.c
#“foo.h”
整数计数 = 6;
无效 foo() {
(“我是富!”);
复制代码
$> gcc -c foo.c
$>ar rv .a foo.o
我们首先创建了一个静态共享库.a,但是在编译Go源文件时遇到了问题:
$> 去构建 foo.go
# -线-
/tmp/go-/-line-.a(foo.cgo2.)(.text): foo: 不是
foo(0): 不是
提示foo函数未定义。 通过-x选项打印了具体的编译细节,但没有发现问题。 不过,我在Go问题列表中发现了一个issue(),其中提到当前版本的Go不支持链接静态共享库。
然后让我们尝试创建一个动态共享库:
$> gcc -c foo.c
$> gcc - -Wl,-,.so -o .so foo.o
再次编译foo.go确实会成功。 执行 foo.
$> 去构建 foo.go && 去
我是福!
还有一点值得注意的是,Go 支持多个返回值,但 C 不支持。 因此,当一个C函数用于具有多个返回值的调用时,C的errno将作为err返回值返回。 下面是一个例子:
复制代码
主要的
// #
// #
// #
// int foo(int i) {
// 错误号 = 0;
// 如果 (i > 5) {
// 错误号 = 8;
// 我 - 5;
// } 别的 {
//我;
// }
//}
“C”
“FMMT”
函数主() {
我,错误:= C.foo(C.int(8))
如果错误!= nil {
fmt。(错误)
} 别的 {
fmt.(i)
复制代码
$> 去运行 foo.go
执行错误
errno为8,其含义可以在errno.h中找到:
# 8 /* 执行错误 */
这确实是一个“执行错误”。
4. 在 C 中使用 Go 函数
与在Go中使用C源代码相比,在C中使用Go函数的场合较少。在Go中,可以使用“+函数名称”导出Go函数以在C中使用。看一个简单的例子:
复制代码
主要的
/*
#
空白 ();
无效栏(){
(“我是酒吧!”);
();
*/
“C”
“FMMT”
//
函数(){
fmt。(“我是一个!”)
函数主() {
C.bar()
复制代码
但是当我们编译Go文件时,我们得到了以下错误消息:
# -线-
/tmp/go-/-line-/_obj/bar.cgo2.o:在“bar”中:
./bar.go:7: 属于“bar”
/tmp/go-/-line-/_obj/.o:/home//test/go/bar.go:7:首先在这里
: ld 1 退出
代码看似没有问题,但就是无法编译,总是提示“多重定义”。 翻阅Cgo文档,我发现了一些线索,原来是
有一个:如果您使用任何 // ,那么其中的 C 代码可能只是 ( int f();),而不是 (int f() { 1; })。
看来 // int f() 和 // f 不能放在 Go 源文件中。 我们将 bar.go 分成两个文件:bar1.go 和 bar2.go:
// 酒吧1.go
复制代码
主要的
/*
#
空白 ();
无效栏(){
(“我是酒吧!”);
();
*/
“C”
函数主() {
C.bar()
复制代码
// 酒吧2.go
复制代码
主要的
“C”
“FMMT”
//
函数(){
fmt。(“我是一个!”)
复制代码
编译并执行:
$> go build -o bar bar1.go bar2.go
$>酒吧
我是酒吧!
我是一个!
就我个人而言,我觉得Go导出函数在C中使用的能力仍然非常有限。 两种语言的调用约定不同,类型无法一一对应,而且Go中的Gc这样的高级函数很难完美实现导出Go函数的功能。 功能仍然无法完全脱离Go环境,实用性似乎受到了影响。
5、其他
尽管Go提供了与C强大的互操作功能,但它仍然不完善。 比如不支持直接调用Go中参数个数可变的()等(因此文档中经常使用fputs)。
这里的建议是:尽量缩小Go和C之间的互操作范围。
这是什么意思? 如果你在Go中使用C代码,那么尝试在C代码中调用C函数。 Go 最好只使用您封装的 C 函数。 不要看起来像下面的代码:
C.fputs(…)
C.阿托伊(..)
C。(..)
相反,这些C函数调用被封装成一个C函数,而Go只知道这个C函数。
C.foo(..)
相反,在 C 中使用 Go 导出函数也是如此。
【Go与C语言的操作】相关文章:
C语言低级运算10-07
C语言位运算有11-24位
C语言文件操作函数11-04
C语言中什么是位运算?10-06
C语言文件操作中fgets和fputs函数讲解10-22
C语言文件操作函数详解11-20
C语言与-05
使用c语言操作文本的基本方法11-20
C语言文件操作分析及示例代码详解10-03