Go 调用 C/C++ 函数全攻略

简介: Go 调用 C/C++ 函数全攻略

一、Go 语言调用 C/C++函数

1. cgo 基础及工作原理

Go 语言通过 cgo 和 C 语言的 ABI(Application Binary Interface)进行交互。

cgo 会生成相应的 C 代码,与 Go 代码一起编译成可执行文件或动态库。

cgo 的工作流程主要分为 3 步:

(1) 预处理:将 Go 源码中的 C 代码块提取出来,生成 .c 和 .h 文件

(2) 编译:调用 C 编译器,分别编译 C 文件和 Go 文件

(3) 链接:将 C 文件和 Go 文件链接生成最终的可执行程序

通过这种方式, Go 代码就可以直接调用 C 代码中的函数,两个语言实现了无缝衔接。

2. Go 调用 C 动态库示例

下面是一个 Go 程序调用 C 语言编写的动态库 libadd.so 的示例


package main
//#include <stdio.h>//int add(int a, int b);import "C"import "fmt"
func main() {    res := C.add(10, 20)    fmt.Println(res)}

libadd.c


int add(int a, int b) {  return a + b; }

编译运行


$ gcc -fPIC -shared -o libadd.so libadd.c$ go build demo.go -ldflags="-L." -gccgoflags="-Wl,-rpath,."$ ./demo30

通过 cgo 和 ABI,Go 程序可以无障碍调用 C 语言编译而成的动态库。

3. Go 调用 C++方法示例

Go 调用 C++函数与调用 C 函数基本类似,也需要通过 cgo 进行 ABI 层的适配。

主要不同在于 C++编译器的支持和名称修饰(name mangling)问题。

示例代码


package main
//#include "add.h"import "C"import "fmt"
func main() {    res := C.add(10, 20)      fmt.Println(res)}

add.h


#ifdef __cplusplusextern "C" {#endif
int add(int a, int b);
#ifdef __cplusplus}#endif

add.cpp


int add(int a, int b) {  return a + b;}

编译运行


$ g++ -fPIC -shared -o libadd.so add.cpp$ go build demo.go -ldflags="-L." -gccgoflags="-Wl,-rpath,."  $ ./demo30

通过外部 "C" 包装,解决了 C++名称修饰的问题,使得 Go 可以正确调用 C++函数。

4. 数据类型映射及类型不安全

Go 和 C/C++ 中的基础数据类型可以通过 cgo 自动映射,如:

Go int、uint 对应 C/C++ int、unsigned int

Go byte 对应 C/C++ char

Go string 对应 C/C++ char*

但是也存在一些类型无法精确映射的情况,例如 Go 的 string 和 slice 类型,会映射为 C 语言的指针,这种转换是类型不安全的,需要开发者自行处理。

此外,复杂数据结构的自定义类型也需要开发者自行转换处理。


 

二、C/C++调用 Go 函数

1. cgo 生成头文件供 C/C++使用

cgo 可以为 Go 包生成对应的 C 语言头文件,供 C/C++ 代码调用。

示例


package main
//export Addfunc Add(a, b int) int {    return a + b}
func main() {}

生成头文件


$ go tool cgo example.go

example.h


extern int Add(int a, int b);

2. C/C++主调 Go 函数示例

main.c


#include "example.h"#include <stdio.h>
int main() {  int res = Add(10, 20);  printf("%d\n", res);  return 0;  }

编译链接


$ gcc -o app main.c example.o

输出


30

通过 cgo 生成的头文件,C/C++ 代码可以无缝调用 Go 语言编写的函数。

3. Go 函数性能考量

与 Go 相比, C/C++ 函数调用性能更高,而 Go 函数调用会有一定的性能损耗。

所以在混合编程时,对性能敏感的功能应优先使用 C/C++实现,非核心流程可以使用 Go 实现。


 

三、Go 与 C/C++的数据交换

1. 数组及切片传递为 C 指针

Go 语言中的数组和切片可以直接转换为 C 语言中的指针进行传递。

示例代码


package main
//#include <stdio.h>//void print_array(int* arr, int len);import "C"import "unsafe"
func main() {    arr := []int{1, 2, 3, 4, 5}
    C.print_array    ((*C.int)(unsafe.Pointer(&arr[0])), C.int(len(arr)))}

print_array 函数定义


void print_array(int* arr, int len) {  for (int i = 0; i < len; i++) {    printf("%d ", arr[i]);   }}

输出


1 2 3 4 5

Go Slice 直接转换为 C 指针,两种语言实现了无缝对接。

2. 字符串处理差异及转换

Go 中的字符串为 UTF-8 编码,而 C/C++使用空终止符结束。字符串处理有一定区别。

简单示例


cs := C.CString("Hello")defer C.free(unsafe.Pointer(cs))
cstr := C.GoString(cs)

通过 C.CString 和 C.GoString 可以安全地在两种字符串表示之间转换。

3. 结构体互相映射机制

Go 语言中的结构体可以与 C 结构体互相映射,前提是两个结构体的定义是兼容的。

示例代码


package main
/*struct MyData {  int a;  char b;  float c;  };*/import "C"import "unsafe"import "fmt"
type MyData struct {    a int    b byte    c float32}
func main() {    v := MyData{1, 'a', 2.5}    cv := (*C.struct_MyData)(unsafe.Pointer(&v))    
    fmt.Println(cv.a) // 1    fmt.Println(cv.b) // 'a'    fmt.Println(cv.c) // 2.5  }

Go 与 C 结构体直接转换为指针使用,成员按内存分布顺序访问。这实现了两种语言自定义类型字段的无缝映射。


 

四、Go 与 C/C++的内存管理

1. Go 自动内存回收机制

Go 语言采用高效的垃圾回收机制来自动管理内存。不需要开发者手动分配释放内存,避免了常见的内存错误,例如泄漏、重用已释放内存等。

Go GC 主要采用标记清除算法,由垃圾回收器在后台定期自动执行,不会暂停应用程序线程。可以实现很低的 GC 延迟。

2. C/C++手动内存管理

C/C++要求开发者手动动态分配和释放堆内存。容易出现内存泄漏和重用悬空指针等严重 Bug。

智能指针等技术可部分缓解问题。

手动内存管理的好处是可以细粒度控制内存使用,优化性能。且不存在 GC 停顿的问题。

3. 内存安全和性能的权衡

Go 自动 GC 实现了内存安全防护,降低了开发难度,但是会有一定的性能损耗。

而 C/C++手动内存管理可以利用复杂手段优化内存和性能,但开发难度大且易出错。

综合来说,Go 更适合互联网服务端开发,而 C/C++更擅长一些对性能要求极高的场景,如游戏、图像处理等。


 

五、Go 与 C/C++进程间通信

1. 基于文件/管道的 IPC

Go 和 C/C++可以通过文件、命名管道(FIFO)、匿名管道实现进程间通信。

文件可实现数据持久化,管道支持全双工通信。都存在一定的时间和空间开销。

2. 基于 Socket 的网络通信

Socket 套接字可实现高效的网络数据传输,支持任意倍扩展。是跨语言混合编程的理想 IPC 方式。

Go 语言实现 socket 编程很高效,与 C/C++可完美配合。

3. 基于 RPC 的跨语言调用

Go 语言和 C/C++都有成熟的 RPC 框架,可以实现高性能的进程/网络间通信,构建分布式系统。

例如 Go 语言的 gRPC 框架,已经成为 CNCF 基金会的毕业项目,在云原生领域得到广泛应用。


 

六、Go 与 C/C++混合编程案例分析

1. 异步 IO 网络服务

服务端可以用 Go 实现,利用 Go 协程实现高并发。再通过 C 优化底层 IO 性能。这样既保证了易用性,又兼顾了性能。

2. 图形图像处理

图像处理算法利用 C/C++编写,并行计算由 Go 实现,两种语言各司其职,共同完成任务。


 

七、优缺点和注意事项分析

Go 与 C/C++混合编程,综合了两者的优势,但也需要注意一些问题。

优点包括:

提高性能: 计算密集型任务用 C/C++,业务逻辑用 Go

复用代码: 大量成熟 C/C++代码可复用

降低难度: Go 易用性提高整体开发效率

缺点和注意事项:

类型转换: 类型映射不完全,需要处理

调试复杂: 跨语言代码调用链导致 debug 难度增大

内存安全: C/C++代码中可能出现各类内存问题

并发处理: C/C++部分需要注意线程安全

总体而言,Go 与 C/C++互补优势多于竞争关系,谨慎适当混合可以取得最好效果。


 

八、总结

通过 cgo 和 ABI,Go 语言可以非常便利地与 C/C++ 进行交互调用,实现两种语言的优势互补。

数据类型基本可以无缝映射,通过指针和 RPC 可高效通信。

Go 自动内存管理提高了安全性,C/C++手动优化可取得更高性能。

在网络服务、计算任务等场景,混合编程可以充分发挥各自语言的长处。

Go 与 C/C++的交互坐标,是当前跨语言编程的重要内容。

目录
相关文章
|
18天前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
4月前
|
人工智能 Dart Go
Go语言中的make和new函数的区别及使用场景
本文详细解析了Go语言中`make`和`new`函数的使用方法及区别。`make`用于创建切片、映射和通道等引用类型,返回初始化后的值;`new`用于创建任意类型的零值对象,返回指向该对象的指针。文章通过多个示例说明两者的应用场景,并总结了面试中可能遇到的相关问题,如底层实现、使用场景及优缺点等,帮助读者更好地理解和区分这两个函数。
121 1
|
5月前
|
Go 调度
GO语言函数的内部运行机制分析
以上就是Go语言中函数的内部运行机制的概述,展示了函数在Go语言编程中如何发挥作用,以及Go如何使用简洁高效的设计,使得代码更简单,更有逻辑性,更易于理解和维护。尽管这些内容深入了一些底层的概念,但我希望通过这种方式,将这些理论知识更生动、更形象地带给你,让你在理解的同时找到编程的乐趣。
77 5
|
5月前
|
Go Python
函数的定义与调用 -《Go语言实战指南》
本文介绍了 Go 语言中函数的核心特性与用法,包括基本定义格式、调用方式、多返回值、返回值命名、参数类型简写、可变参数、高阶函数及匿名函数等内容。通过示例代码详细展示了如何定义和使用不同类型的函数,使读者能够全面了解 Go 函数的灵活性与强大功能。
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
95 0
|
7月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
359 6
|
11月前
|
JSON 安全 网络协议
go语言使用内置函数和标准库
【10月更文挑战第18天】
142 3
|
12月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
12月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
446 6
|
12月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
109 3