Go 与 C 语言的互操作

本文详细探讨了Go语言与C语言之间的互操作性,包括Go调用C代码的原理,如何在Go中使用C的类型如数值、指针、字符串、数组和自定义类型。此外,还介绍了Go中访问C变量和函数的方法,以及C如何调用Go导出的函数。尽管Go提供了强大的互操作支持,但也存在一些限制,如不支持可变参数函数。建议尽量减少直接的互操作,避免过度耦合。
摘要由CSDN通过智能技术生成

1. Go 与 C 语言的互操作

Go 有强烈的 C 背景, 除了语法具有继承性外, 其设计者以及其设计目标都与 C 语言有着千丝万缕的联系。在 Go 与 C 语言互操作 (Interoperability) 方面, Go 更是提供了强大的支持。尤其是在 Go 中使用 C, 你甚至可以直接在 Go 源文件中编写 C 代码, 这是其他语言所无法望其项背的。

在如下一些场景中, 可能会涉及到 Go 与 C 的互操作:

  1. 提升局部代码性能时, 用 C 替换一些 Go 代码。C 之于 Go, 好比汇编之于 C。
  2. 嫌 Go 内存 GC 性能不足, 自己手动管理应用内存。
  3. 实现一些库的 Go Wrapper。比如 Oracle 提供的 C 版本 OCI, 但 Oracle 并未提供 Go 版本的以及连接 DB 的协议细节, 因此只能通过包装 C OCI 版本的方式以提供 Go 开发者使用。
  4. Go 导出函数供 C 开发者使用(目前这种需求应该很少见)。
  5. Maybe more…

1.1. Go 调用 C 代码的原理

下面是一个短小的例子:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
/*
void print(char *str) {
    printf("%s\n", str);
}
*/
import "C"
 
import "unsafe"
 
func main() {
   
    s := "Hello Cgo"
    cs := C.CString(s)
    C.print(cs)
    C.free(unsafe.Pointer(cs))
}

与 "正常"Go 代码相比, 上述代码有几处 “特殊” 的地方:

  1. 在开头的注释中出现了 C 头文件的 include 字样
  2. 在注释中定义了 C 函数 print
  3. import 的一个名为 C 的 “包”
  4. 在 main 函数中居然调用了上述的那个 C 函数 - print

没错, 这就是在 Go 源码中调用 C 代码的步骤, 可以看出我们可直接在 Go 源码文件中编写 C 代码。

首先, Go 源码文件中的 C 代码是需要用注释包裹的, 就像上面的 include 头文件以及 print 函数定义;
其次, import “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 识别并使用的。

1.2. 在 Go 中使用 C 语言的类型

1.2.1. 原生类型

1.2.1.1. 数值类型

在 Go 中可以用如下方式访问 C 原生的数值类型:

C.char,
C.schar (signed char),
C.uchar (unsigned char),
C.short,
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.long,
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
C.float,
C.double

Go 的数值类型与 C 中的数值类型不是一一对应的。因此在使用对方类型变量时少不了显式转型操作, 如 Go doc 中的这个例子:

func Random() int {
   
    return int(C.random())//C.long -> Go 的 int
}
 
func Seed(i int) {
   
    C.srandom(C.uint(i))//Go 的 uint -> C 的 uint
}
1.2.1.2. 指针类型

原生数值类型的指针类型可按 Go 语法在类型前面加上 *, 比如 var p *C.int。而 void * 比较特殊, 用 Go 中的 unsafe.Pointer 表示。任何类型的指针值都可以转换为 unsafe.Pointer 类型, 而 unsafe.Pointer 类型值也可以转换为任意类型的指针值。unsafe.Pointer 还可以与 uintptr 这个类型做相互转换。由于 unsafe.Pointer 的指针类型无法做算术操作, 转换为 uintptr 后可进行算术操作。

1.2.1.3. 字符串类型

C 语言中并不存在正规的字符串类型, 在 C 中用带

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云满笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值