Golang在底层系统和嵌入式编程中相比C/C++有哪些劣势?

Golang在底层系统和嵌入式编程中相比C/C++有哪些劣势? 我来自Python背景,经验尚浅。

我发现Go因其庞大的生态系统而非常出色,但当我搜索它在底层系统和系统编程中的应用时,我看到了这条评论

我个人不推荐它。我最近在做一个编程控制摄像头的副项目,中途换成了C++。

主要的痛点:

    ioctl功能是半成品。在类Unix操作系统中,一些内核函数会要求你传递一个指针给ioctl。https://golang.org/x/sys 不支持这个,所以你不得不使用C的ioctl。

    IntelliJ/Goland对cgo的IDE支持非常差。它在类型推断上挣扎得非常厉害,几乎到了放弃的地步。

    你正在与运行时争夺对void指针的控制权,而且很难判断哪些代码会导致内存问题,哪些不会。

在我看来,你最好直接使用C++。

ioctl功能真的像他说的那样是半成品吗?

会要求你传递一个指针给ioctl。https://golang.org/x/sys 不支持这个

这条评论是三年前的,但Go编程语言现在已经实现这个功能了吗?


更多关于Golang在底层系统和嵌入式编程中相比C/C++有哪些劣势?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

我发现Go在交叉编译方面非常出色,过程简单且轻松。

更多关于Golang在底层系统和嵌入式编程中相比C/C++有哪些劣势?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我观看了整个视频,谢谢。

如果几十年后,没有人想到创建一种几乎通用的、能为多个平台生成二进制文件的语言,那将是非常遗憾的。这门语言就是 Go。

附言:我相信并不存在一种真正通用的语言。

精细控制:C/C++ 提供了对内存管理和底层操作的精细控制,这在某些底层系统和嵌入式编程场景中至关重要。而 Go 则抽象掉了许多底层细节,使其更易于使用,但可能会牺牲一些精细控制。

我不是专业程序员,而是一名硬件专家。对我来说,越容易编程的语言就越有用。当我发现TinyGo和Golang时,我被深深震撼了。它们学习起来简单,使用起来也方便。至于RUST,我一点也不喜欢,因为它需要更多的编程知识,而这并非我的主要兴趣所在。

是的——如果你想了解更多关于为什么交叉编译如此轻松的原因,请查看这个时间戳的视频:

GopherCon 2016: Rob Pike - The Design of the Go Assembler

如果你的整个程序都是关于调用 C/C++ 函数,那么 Go 可能就不值得了。

这确实是问题的核心。再补充一点:如果我现在要学习一门用于系统编程的语言,那很可能是 Rust。如果你想站在技术前沿并且喜欢更类似 Go 的语法,也许可以考虑 Zig。无论如何,祝你在学习之旅中好运,如果你选择使用 Go,欢迎随时回来交流!

如果我现在要学习一门用于系统编程的语言,那很可能是 Rust

这取决于你在嵌入式领域从事什么工作(微型计算机 vs. 微控制器)。如果是前者,一切都应该像普通计算机一样运行,因此只需选择你选择的硬件支持的语言。如果情况确实很特殊,那么你很可能在使用非常规的或专用的计算硬件(这在 ARM 单板机市场很常见,有时你能买到行为类似路由器的板子)。

如果是后者,基于 LLVM 的编译器会遇到麻烦,因为芯片制造商(例如 Microchip 分发 XC 编译器)愿意支持 LLVM 的情况极其罕见。我最近将 Rust 从列表中剔除,部分原因是这个,但主要是因为其过于权威的治理方式(空谈太多,管得太宽,富有成效的行动太少)。

我仍然保留 Go,是因为它的算法和 TinyGo。到目前为止,Nim 做得更好,它可以将代码转译为 C 代码供专有编译器使用。请记住,TinyGo 仍在开发中,但我对该项目充满信心。

IDE support for cgo is really bad for IntelliJ/Goland. It struggles really hard with typing to the point it almost just gives up.

关于 IDE 的问题——它只是一个文本编辑器。换用另一个就是了。代码和知识产权并非由 Microsoft (VSCode) 或 IntelliJ 或 [在此处插入你付费的编辑器] 垄断。如果需要,可以使用 vim/nano。

因为 IDE 支持差就声称某些东西无法工作,这是非常不成熟和幼稚的。真正的工作是由编译器/解释器、代码检查工具和代码库完成的。这很奇怪,你为一个失败了的文本编辑器支付了那么多钱,却没有向那些真正起作用的程序捐款。

我并非消极。当你进行底层开发时,你会面临很多挫折(例如,厚重的数据手册、与制造商的客户服务沟通、在没有可观测仪器的情况下调试、调度、周期时序同步……),以至于你没有时间处理这些琐碎的无稽之谈。

旁注:但仍应尽可能避免使用 CGo。CGo 很久以前就被弃用了。Go 现在有 GoASM,因此它应该适用于 I/O 驱动级别的接口。

首先声明:我不从事嵌入式系统编程,所以请对我的话持保留态度,并且一如既往地:“这取决于具体情况”,但是:

如果你本质上是在编写一堆C/C++ API之间的“胶水代码”,那么我不推荐使用Go。但是,如果你目前正在用Python进行任何类型的计算工作,那么由于运行原生CPU代码而非解释型Python字节码所带来的性能提升,在C/C++和Go之间切换的开销基本上会变得微不足道。

我不熟悉ioctl,但在 https://pkg.go.dev/golang.org/x/sys/unix 中有很多 Ioctl 函数,其中一些接受指针作为参数,另一些则返回指针。所以我不确定那条评论是否已经过时,或者也许因为它是一个标准库之外的 golang.org/x/... 包,原发帖人可能不知道它,等等。但我不知道这个包是否会改变你的想法。

关于IDE支持,我不能说我感到惊讶。毕竟,cgo不是Go。我希望你只需要编写最少量的cgo代码来包装对C/C++的调用,而其他99%的代码都是普通的Go。如果情况并非如此(例如,如果你的项目只是在向/从C/C++库传递数据之前进行一些转换),那么Go可能不是最佳选择,至少对于那种项目来说是这样。

关于:

你正在与运行时争夺voidptr的控制权,并且很难推理哪些代码会导致内存问题,哪些不会。

我不确定这是什么意思。也许它指的是处理cgo时一个常见的“陷阱”:如果指向的数据本身包含Go指针,则不能将指针传递给C:

type OKForCGo struct {
    data0 uint32
    data1 uint32
    data2 uint32
}

x := OKForCGo{}
C.do_something(&x)

type NotOKForCGo struct {
    ptr0 *uint32
    ptr1 *uint32
    ptr2 *uint32
}

y := NotOKForCGo{
    ptr0: new(uint32),
    ptr1: new(uint32),
    ptr2: new(uint32),
}
// 不行,因为 y.ptr0, .ptr1, 和 .ptr2 是从Go分配的。
C.do_something_else(&y)

但是,如果这些 ptr 字段是用 C.malloc 分配的,那么就没问题,因为它们不是Go指针;它们会是C指针。所以,也许原发帖人指的是判断一个指针是否需要被 C.free 的复杂性,因为你不知道它是Go指针还是C指针?


tl;dr

如果你能用普通的Go(非cgo)编写大部分代码,并且只需要cgo来桥接你的Go代码与操作系统/某些嵌入式硬件之间的鸿沟,那么Go应该没问题。

如果你的整个程序都是关于调用C/C++函数的,那么Go可能就不值得了。

在底层系统和嵌入式编程中,Go相比C/C++确实存在一些劣势,主要源于其运行时和内存模型的设计。以下是具体分析:

  1. ioctl支持golang.org/x/sys包已提供更完整的ioctl支持。例如,现在可以使用unix.IoctlSetInt等函数传递指针参数:
import "golang.org/x/sys/unix"
var ptr uintptr = 0x1234
err := unix.IoctlSetInt(fd, request, int(ptr))

但复杂的数据结构仍可能需要通过cgo调用C函数。

  1. 内存控制限制:Go的垃圾回收器会移动堆对象,无法直接使用原始指针进行硬件访问。对于需要精确内存布局的嵌入式场景(如寄存器映射),必须使用unsafe.Pointer并固定内存:
// 内存映射I/O示例
var reg *uint32
reg = (*uint32)(unsafe.Pointer(uintptr(0x40000000)))
*reg = 0x1 // 可能被GC移动
  1. 实时性限制:Go的GC暂停时间(通常<1ms)对多数嵌入式系统仍过长。硬实时系统要求微秒级响应,例如:
// Go无法保证以下代码的精确执行时间
func realtimeTask() {
    start := time.Now()
    // 关键操作
    if time.Since(start) > 100*time.Microsecond {
        // 可能违反实时约束
    }
}
  1. 二进制大小:最简单的Go程序也包含运行时,最小约1.5MB。而C程序可做到几KB:
# Go最小可执行文件
go build -ldflags="-s -w" main.go  # 约1.5MB
# C最小可执行文件
gcc -static -Os main.c -o main     # 约10KB
  1. cgo开销:每次C/Go调用转换约有50ns开销。高频调用时影响显著:
// 频繁的cgo调用示例
/*
extern void c_function();
*/
import "C"
func GoCall() {
    for i := 0; i < 1000000; i++ {
        C.c_function() // 每次调用都有转换开销
    }
}
  1. 硬件特性访问:无法直接使用内联汇编访问特定寄存器。必须通过汇编文件:
// go:linkname声明
func readCR0() uint64
// 对应的汇编文件
TEXT ·readCR0(SB), NOSPLIT, $0
    MOVQ CR0, AX
    RET

虽然Go通过x/sys包改进了系统调用支持,但在需要直接硬件操作、确定内存布局或硬实时响应的场景中,C/C++仍是更合适的选择。Go更适合应用层系统编程,如网络服务或工具开发。

回到顶部