咱们好,我是煎鱼。
最近因为各种古怪的原因,接触到了 Go 特色之一 CGO。这方面的相关内容也相对少一些,给咱们抛砖引玉。
究竟许多跨言语调用,仍是会依赖 CGO 这个特性。希望咱们在真实要用时有个前置知识垫肚子。
CGO 是什么
CGO 就是 C 和 Go,两个编程言语。指的是能够创建调用 C 代码的 Go 包。对照着 Go 代码中的 “C”:
package main
import "C"
func main() {}
一旦程序中呈现 import "C"
,则意味着敞开 CGO 特性。在进行 go build
等阶段时,将会调用 C 编译器(通常是 gcc 或 clang)。
CGO 对应的环境变量是 CGO_ENABLED,设置为 1 则敞开 CGO,为 0 则封闭 CGO。
编译指令如下:
CGO_ENABLED=0 go build -o hellojy main.go
当然,对于默许值。该环境变量值为 1,C 编译器也是运用 gcc。咱们能够经过 go env 看到:
一旦封闭就会影响 CGO 编译。需求特别留心,交叉编译时会默许封闭 CGO。
CGO 快速上手
最小 Demo
先来一个 CGO 的 Go 例子:
package main
//#include <stdio.h>
import "C"
func main() {
s := C.CString("hello world.")
C.puts(s)
}
运转 go run main.go
,输出结果:
hello world.
声明 C 注解
假如你没有了解过 CGO,看到上面的例子,可能会有好几个疑问。
首先是 include:
//#include <stdio.h>
import "C"
import "C"
咱们懂,是导入 C 的伪包。 前面的注解是什么?
无论是:
//#include <stdio.h>
又或是:
/*
#include <stdio.h>
#include <stdlib.h>
*/
实际上这是导入 C 前的注解,注解内容能够包括任何 C 代码,例如:函数、变量的声明定义、库引证等。(该注解要紧挨导入句子)
回到 Demo 自身,假如咱们去掉 //#include <stdio.h>
,再运转会呈现如下报错:
# command-line-arguments
./main.go:7:2: could not determine kind of name for C.puts
去掉后,句子 C.puts(s)
将无法运转。
实际上 stdio.h
的全称是:standard input output.header(标准输入输出头文件)。该文件大都是些输入输出函数的声明,引证了这库,就能运用 C 的 puts 办法。
其他同理,你在注解中声明、定义的东西,均能够在 Go 代码中经过 C 这个伪包来引证和调用。
其次像是 CString 办法,归于在 Go 和 C 类型之间需求仿制数据的特殊函数,伪包 C 有进行预定义。
例如:
func C.CString(string) *C.char
func C.CBytes([]byte) unsafe.Pointer
func C.GoString(*C.char) string
func C.GoStringN(*C.char, C.int) string
func C.GoBytes(unsafe.Pointer, C.int) []byte
Go 和 C 类型对照
Go 官方有提供一份根底类型的对照表,咱们能够参照来运用和了解。
如下:
C 言语类型 | CGO 类型 | Go言语类型 |
---|---|---|
char | C.char | byte |
singed char | C.schar | int8 |
unsigned char | C.uchar | uint8 |
short | C.short | int16 |
unsigned short | C.ushort | uint16 |
int | C.int | int32 |
unsigned int | C.uint | uint32 |
long | C.long | int32 |
unsigned long | C.ulong | uint32 |
long long int | C.longlong | int64 |
unsigned long long int | C.ulonglong | uint64 |
float | C.float | float32 |
double | C.double | float64 |
size_t | C.size_t | uint |
注意事项
运用 CGO,除了会带来必定的性能损耗外。需求特别注意的是:内存泄露。因为 Go 是带废物收回机制的编程言语,而运用了 C 后,需求手动的办理内存。
仍是这个 Demo:
package main
//#include <stdio.h>
import "C"
func main() {
s := C.CString("hello world.")
C.puts(s)
}
假如这是一个常驻进程,也没有任何开释动作。用 C.CString
办法所请求的变量 s 就会泄露。
因此与 “C” 相关的变量创建,需求进行手动的内存办理。正确的代码如下:
/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
func main() {
b := C.CString("hello world.")
C.puts(b)
C.free(unsafe.Pointer(b))
}
需求调用 C.free
办法进行主动的内存开释。假如该程序天然完毕,也会主动收回。
总结
在今天这篇文章中,咱们介绍了 Go 言语中 CGO 的根底知识和快速入门。整体上,只要习惯了写法,CGO 的用法就不算太费事。
需求特别注意手动内存办理、性能损耗等多方面的制约。后续咱们也会继续深化 CGO 方面的内容。
文章继续更新,能够微信搜【脑子进煎鱼了】阅览,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 言语能够看 Go 学习地图和路线,欢迎 Star 催更。
Go 图书系列
- Go 言语入门系列:初探 Go 项目实战
- Go 言语编程之旅:深化用 Go 做项目
- Go 言语规划哲学:了解 Go 的为什么和规划思考
- Go 言语进阶之旅:进一步深化 Go 源码
推荐阅览
- Go 在信创这一块会输给 Java,想不通。。。
- Go 错误处理:100+ 提案全部被回绝,为何现阶段仍用 if err != nil?
- Go 为什么不像 Rust 用 ?!做错误处理?