首页 > 其他分享 >cgo 机制

cgo 机制

时间:2023-12-24 17:55:36浏览次数:27  
标签:cgo CGO SayHello Go import 机制 main 语言

Go语言是通过自带的一个叫CGO的工具来支持C语言函数调用,同时我们可以用Go语言导出C动态库接口给其它语言使用。

基于 C 标准库实现最简单的 CGO 程序

// hello.go
package main

//#include <stdio.h>
import "C"

func main() {
    C.puts(C.CString("Hello, this is a CGO demo.\n"))
}

基于自己写的 C 函数构建 CGO 程序

下面再来看个例子。先自定义一个叫 SayHello 的 C 函数来实现打印,然后从 Go 语言环境中调用这个 SayHello 函数:

// hello.go
package main

/*
#include <stdio.h>
static void SayHello(const char* s) {
    puts(s);
}
*/
import "C"

func main() {
    C.SayHello(C.CString("Hello, World\n"))
}

除了 SayHello 函数是我们自己实现的之外,其它的部分和前面的例子基本相似。

我们也可以将 SayHello 函数放到当前目录下的一个 C 语言源文件中(后缀名必须是.c)。因为是编写在独立的 C 文件中,为了允许外部引用,所以需要去掉函数的 static 修饰符。

// hello.c

#include <stdio.h>

void SayHello(const char* s) {
    puts(s);
}

然后在 CGO 部分先声明 SayHello 函数,其它部分不变:

// hello.go
package main

//void SayHello(const char* s);
import "C"

func main() {
    C.SayHello(C.CString("Hello, World\n"))
}

其实 CGO 不仅仅用于 Go 语言中调用 C 语言函数,还可以用于导出 Go 语言函数给 C 语言函数调用。

用 C 接口的方式实现 Go 编程

// main.go
package main

//void SayHello(char* s);
import "C"

import (
    "fmt"
)

func main() {
    C.SayHello(C.CString("Hello, World\n"))
}

//export SayHello
func SayHello(s *C.char) {
    fmt.Print(C.GoString(s))
}

虽然看起来全部是 Go 语言代码,但是执行的时候是先从 Go 语言的 main 函数,到 CGO 自动生成的 C 语言版本 SayHello 桥接函数,最后又回到了 Go 语言环境的 SayHello 函数。这个代码包含了 CGO 编程的精华。

CGO 的主要基础参数

import "C" 语句说明
如果在 Go 代码中出现了 import "C" 语句则表示使用了 CGO 特性,紧跟在这行语句前面的注释是一种特殊语法,里面包含的是正常的 C 语言代码。当确保 CGO 启用的情况下,还可以在当前目录中包含 C/C++对应的源文件。比如上面的例子。

#cgo 语句说明

在 import "C" 语句前的注释中可以通过 #cgo 语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。

在 import "C" 语句前的注释中可以通过 #cgo 语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。

为什么要引入 CGO

突破 Go 创建切片的内存限制

由于 Go 语言实现的限制,我们无法在 Go 语言中创建大于 2GB 内存的切片(可参考 makeslice 实现源码)。不过借助 cgo 技术,我们可以在 C 语言环境创建大于 2GB 的内存,然后转为 Go 语言的切片使用:

package main

/*
#include <stdlib.h>

void* makeslice(size_t memsize) {
    return malloc(memsize);
}
*/
import "C"
import "unsafe"

func makeByteSlize(n int) []byte {
    p := C.makeslice(C.size_t(n))
    return ((*[1 << 31]byte)(p))[0:n:n]
}

func freeByteSlice(p []byte) {
    C.free(unsafe.Pointer(&p[0]))
}

func main() {
    s := makeByteSlize(1<<32+1)
    s[len(s)-1] = 255
    print(s[len(s)-1])
    freeByteSlice(s)
}

例子中我们通过 makeByteSlize 来创建大于 4G 内存大小的切片,从而绕过了 Go 语言实现的限制。而 freeByteSlice 辅助函数则用于释放从 C 语言函数创建的切片。

因为 C 语言内存空间是稳定的,基于 C 语言内存构造的切片也是稳定的,不会因为 Go 语言栈的变化而被移动。

方便在 Go 语言中接入使用 C/C++的软件资源

CGO 提供了 golang 和 C 语言相互调用的机制。而在某些第三方库可能只有 C/C++ 的实现,也没有必要用纯 golang 重新实现,因为可能工作量比较大,比较耗时,这时候 CGO 就派上用场了。

被调用的 C 代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接。

这里推荐使用静态库的方式,这样方便代码隔离,也符合 Go 的哲学。

CGO 带来的问题

当你在 Go 包中导入 "C" 时,go build 需要做更多的工作来构建你的代码。

构建时间变长

当你在 Go 包中导入 "C" 时,go build 需要做更多的工作来构建你的代码。

需要调用 cgo 工具来生成 C 到 Go 和 Go 到 C 的相关代码。
系统中的 C 编译器会为软件包中的每个 C 文件进行调用处理。
各个编译单元被合并到一个 .o 文件中。
生成的 .o 文件会通过系统的链接器,对其引用的共享对象进行修正。

构建变得复杂

在引入了 cgo 之后,你需要设置所有的环境变量,跟踪可能安装在奇怪地方的共享对象和头文件等。

另外需要注意,Go 支持许多的平台,而 cgo 并不是。需要安装 C 编译器,而不仅仅是 Go 编译器。而且可能还需要安装你的项目所依赖的 C 语言库,这也是需要技术成本的。

Go 和 C 内存模型不同

内存管理变得复杂,C 没有垃圾回收,而 go 有gc机制,两者的内存管理机制不同,可能会带来内存泄漏。
CGO 是 Go 语言和 C 语言的桥梁,它使二者在二进制接口层面实现了互通,但是我们要注意因两种语言的内存模型的差异而可能引起的问题。
如果在 CGO 处理的跨语言函数调用时涉及到了指针的传递,则可能会出现 Go 语言和 C 语言共享某一段内存的场景。
C 语言的内存在分配之后就是稳定的,但是 Go 语言因为函数栈的动态伸缩可能导致栈中内存地址的移动(这是 Go 和 C 内存模型的最大差异)。如果 C 语言持有的是移动之前的 Go 指针,那么以旧指针访问 Go 对象时会导致程序崩溃。

使用 C 静态库实现

CGO 在使用 C/C++资源的时候一般有三种形式:

直接使用源码;
链接静态库;
链接动态库。

直接使用源码就是在 import "C" 之前的注释部分包含 C 代码,或者在当前包中包含 C/C++源文件。

链接静态库和动态库的方式比较类似,都是通过在 LDFLAGS 选项指定要链接的库方式链接。这里主要关注在 CGO 中如何使用静态库的问题。

总结

CGO其实是 C 语言和 Go 语言混合编程的技术,因此要想熟练地使用 CGO 是非常有必要要了解这两门语言的。

任何技术和语言都有它自身的优点和不足,Go 语言不是银弹,它无法解决全部问题。而通过 CGO 可以做到以下几点:

通过 CGO 可接入 C/C++的世纪软件遗产
通过 CGO 可以用 Go 给其它系统写 C 接口的共享库
通过 CGO 技术也可以让 Go 语言编写的代码可以很好地融入现有的软件生态

而现在的软件确实大多数是建立在 C/C++语言之上的。因此 CGO 可以说是一个统筹兼备的技术,是 Go 的一个重量级的技术,也是值得任何一个 Go 语言开发人员学习的。

标签:cgo,CGO,SayHello,Go,import,机制,main,语言
From: https://www.cnblogs.com/beatle-go/p/17924656.html

相关文章

  • jdk和dubbo的SPI机制
    jdk和dubbo的SPI机制转载自:https://www.cnblogs.com/wyq178/p/12171881.html前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。对......
  • 观察者模式揭秘:实现松耦合的事件通知机制
    推荐语本篇文章深度剖析了观察者模式的核心原理及其在软件开发中的重要应用,通过清晰而深入的讲解,读者小伙伴可以深入理解观察者模式如何实现松耦合的事件通知机制,从而构建更灵活、可扩展的软件系统。本文既适合希望深入了解设计模式的专业人士,也适合希望提升代码质量和可维护性的开......
  • linux修改内核参数禁止OOM机制
    Linux内核有个机制叫OOMkiller(Out-Of-Memorykiller),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽,内核会把该进程杀掉,监控是正常的。防止重要的系统进程触发(OOM)机制而被杀死:可以设置参数/proc/PID/oom_adj为-17,临时关闭linux内核的OOM机制......
  • Eureka Server 自我保护机制
    https://blog.csdn.net/u012410733/article/details/112303048下面是官方提供的Eureka架构图: 1、什么是自我保护机制默认情况下,如果EurekaServer在一定时间内(默认90秒,其实不止90秒)没有接收到某个微服务实例的心跳,EurekaServer将会移除该实例。但是当网络分区故障发生......
  • centos6和7的模板机制作
    centos6(安装操作系统直接最小化安装就行)1.进入网卡配置文件将网卡的MAC和UUID删除(网卡需要开机自启的话,只要把ONBOOT=no改为ONBOOT=yes就行)2.挂在光盘,临时挂在3.制作yum源yumcleanall#清楚yum源的缓存yummakecache#生成新的yum源yum -y install g......
  • 2-2自动微分机制
    0.配置神经网络通常依赖反向传播求梯度来更新网络参数,求梯度过程通常是一件非常复杂而容易出错的事情。而深度学习框架可以帮助我们自动地完成这种求梯度的运算。Pytorch一般通过反向传播backward方法实现这种求梯度计算。该方法求得的梯度将存在对应自变量张量的grad属性下......
  • 【浏览器】渲染机制
    要想理解浏览器的运行环境,我们先要搞明白一些计算机组件以及它们的作用。CPU它可以串行地一件接着一件处理交给它的任务。很久之前的时候大多数CPU只有一个核心,不过在现在的硬件设备上CPU通常会有多个核心,因为多核心CPU可以大大提高手机和电脑的运算能力。GPU图形处理器-或......
  • 双亲委派机制
    定义双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。很多人对“双亲”一词很困惑。这是翻译的锅。“双亲”只是“parents”的直译,实际上并不表示汉语中的父母双亲,而是一代一代很多parent,即parents。描述:当某个类加载器需要加载某个.class文......
  • 注意力机制打印cam
    importcv2importnumpyasnpfromPILimportImage#加载示例图像image_path='path/to/your/image.jpg'image=Image.open(image_path).convert('RGB')#预处理图像,以符合模型的输入要求preprocess=transforms.Compose([transforms.Resize((224,224)),......
  • 6. 类加载机制
    类加载机制虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制与那些在编译时需要进行连接的语言不同,在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间......