比较GO和C

选中Go语言进行开发,还源于它极其丰富的标准库。这一优势有助于开发进程的加快,特别是与C语言相比。Go语言从C、C++和Python继承了许多元素,包括表达式语句,控制流语句,数据结构,接口,指针,引用传递概念,参数解析语法,字符串处理和垃圾回收机制。凭借其优化的编译器,Go 可在嵌入式设备上实现代码本地运行。

当然,Go语言不可避免的会有一些缺点,后面也将提及。但瑕不掩瑜,这并不影响Go语言明显的优势,比如在开发速度和语言构建方面的便利性。这些对于评估团队的选择也有所影响,下面逐一来看。

Go和C代码体积比较

在代码体积方面,Go不如C轻便,这是它为数不多的缺点之一。以最简单的应用程序“hello world”为例,在Go使用内置printIn函数编写,去除调试符号后,它的大小为刚刚超过600KB(代码段1)。如果将fmt包及其依赖项包括在内,则大小将增至 1.5 MB(代码段 2)。

与之相比较,使用C语言的话,如果是构建一个动态链接库,则它的大小只有8KB。但如果使用静态链接库,则大小将增加到800 KB以上,比较令人意外的是它比Go二进制文件(代码段3)还要大。

代码段1:最基本的 Go语言 “hello world” 代码:

复制

package main

func main() {

println("hello world")

}

$ go build

> 938K

$ go build -ldflags ‘-s -w’

> 682K

$ go build & strip

> 623K

代码段2:标准的Go语言“hello world” 代码:

复制

package main

import "fmt"

func main() {

fmt.Println("hello world")

}

$ go build

> 1,5M

代码段3:C语言“hello world” 代码:

复制

#include <stdio.h>

int main(void)

{

printf("hello world\n");

return 0;

}

$ gcc main.c

> 8,5K

$ ldd a.out

> linux-vdso.so.1

> libc.so.6

> /lib64/ld-linux-x86-64.so.2

$ gcc -static main.c

> 892K

$ gcc -static main.c & strip

> 821K

Go与C的运行速度比较

编译后的Go代码运行起来通常会比C语言的可执行文件慢。Go是具备完全垃圾收集机制的,生来就影响了运行速度。使用C语言时可以精确地指定为变量分配的内存的位置,可以具体到该变量是在栈上还是在堆上;而使用Go时,编译器会自动处理将变量分配在何处。程序员可以看到变量的分配位置 (go build -gcflags -m),但不能强制编译器仅使用摸个位置比如栈。

但是,速度不是仅限于上述,还要考虑到编译速度和开发速度。Go提供了极快的编译速度。比如,15,000行代码Go客户端仅需1.4秒就能编译完毕。并且,Go是为并发执行(goroutines和channels)而设计的,前面阐述过的丰富的标准库能够涵盖大部分基本需求,因此有利于程序员开发速度加快。

编译和交叉编译

有两种编译器可供程序员选择使用:原始编译器叫做gc ,默认安装程序自带,由Google编写和维护。第二种叫做gccgo,是GCC的前端。gccgo使用起来编译速度极快,大型模块通常几秒钟就可以编译完毕。如前所述,Go默认是以静态库编译,这样就可以无需额外依赖项或虚拟机创建单个二进制文件。可以使用-linkshared标志创建和使用共享库。通常,Go不需要构建文件,如需构建则可通过使用简单的“go build”命令执行,高级复杂的构建可以使用Makefile。这里是在Mender使用的Makefile。

除此之外,Go在交叉编译方面支持众多操作系统和架构。图 3 示例了 Go 支持的各种操作系统和平台。

图3 Go 支持的各种操作系统和平台

在 Go 中调试和测试

许多开发人员使用GDB监视程序执行情况。对于高并发Go应用程序,使用GDB调试时会出现一些问题。万幸的是,一个名为 Delve 的专用Go调试器是可以完美使用的,确保了即使是只会使用GDB的开发人员大多数情形下也能轻松调试。

Go代码中添加测试和单元测试十分简单,测试是内置在Go语言中的——只需创建一个带有“test”后缀的文件,为函数添加测试前缀,导入测试包,然后运行“go test”。所有测试就将自动从源中获取并相应执行。

并发支持

Go语言对并发支持特别友好。它有两个内置调度机制:goroutines和channels。goroutines是轻量级线程,仅仅2 kB。创建非常容易:只需要在函数前面添加“go”关键字,它就会同时执行。Go有自己的内部调度器,可以根据需要将goroutines多路复用到OS线程中。channels是用于在 goroutine 之间交换消息的“管道”,可以是阻塞的,也可以是通行的。

静态和动态库:Go语言中的的 C 代码

Go有许多鲜为人知的功能,允许程序员使用特殊标志将指令发送给编译器、连接器和工具链的其他部分。其中包括-buildmode和-linkshared标志,二者皆可用于为Go和C代码段创建并使用静态和动态库。通过特定标志的组合,程序员可以使用生成的C语言头文件创建静态或动态库,这些头文件以后可由C代码调用。

是的,没错!使用Go,可以调用C代码并拥有两全其美的功能。Go发行版包含一个工具名为cgo,可以执行C代码,这使得重用程序员自己的或系统的C库成为可能。实际上,Cgo以及C系统库的间接引用是十分常见的。某些情况下,标准C库例程就是由Go语言构造语法本身从Go实例中选取。

例如,在Unix系统上使用网络包时,很有可能会选择基于cgo的解析器。它会调用C库例程 ( getaddrinfo和 getnameinfo)来解析无法使用本机Go解析器的名称(例如在 OS X上直接 DNS 调用被禁用时);另一种常见情况则是使用系统/用户程序包时。如果想跳过构建cgo部件,程序员可以通过osusergo和netgo标记 (go build -tags osusergo,netgo)或使用CGO_ENABLED=0变量完全禁用 cgo。

将C和Go混合在一起时,还需要注意一些比较棘手的事情。由于Go是有垃圾回收机制的而 C不是,所以如果在Go代码中使用了任何分配在C代码堆栈上的C变量,都必须在 Go 中声明释放。

通过使用接口生成工具(SWIG)来实现,C++也可以在Go代码中使用。SWIGC能够将Go与C++以及Python等语言融合在一起。

Go的优点大于缺陷

从以往的经验可以得出Go既有优点也有缺陷。就负面因素来讲,社区中有很多外部库,但良莠不齐,需要非常小心辨别使用。随着Go语言的成熟和广泛采用,这种情况正在迅速改善。

此外,当Go代码中存在一些C绑定时,事情通常会变得更为复杂,尤其是交叉编译,如果不导入整个C交叉编译基础结构,就难以完成。

总体而言,使用Go进行嵌入式开发还是有很多优点的。从C或Python(或二者兼有)过渡到Go并在短期内使用是快速简单而可行的。Go语言提供了非常优秀的工具和一个包含100多个软件包的标准库。程序员可以轻松控制和设置运行参数,例如,要使用的操作系统线程数(GOMAXPROCS),打开或关闭垃圾回收 (GOGC=off)等。库以及代码可以在服务器和客户端开发团队之间轻松共享,就像文中提及的实例那样。

这样说可能会引起争论,但一致的编码标准事实上加快了开发速度。无论程序员要检查的代码是什么,它看起来是一致的。无需耗费时间在思考使用哪种编码标准上。

工程师们开发了一些嵌入式Go应用程序的演示,可以应用在新兴设备上(例如Beaglebone Black或Raspberry Pi):其中一个名为“恒温器”,基于Beaglebone硬件,使用红外距离、温度和湿度传感器,并可以通过 Web界面读取所有传感器读数;还有一个是基于PRi的机器人汽车,带有摄像头,可以通过Web界面进行控制。喜欢就去享受吧!

译者介绍

张哲刚,51CTO社区编辑,系统运维工程师,国内较早一批硬件评测及互联网从业者,曾入职阿里巴巴。十余年IT项目管理经验,具备复合知识技能,曾参与多个网站架构设计、电子政务系统开发,主导过某地市级招生考试管理平台运维工作。

原文标题:Comparing Go vs. C in embedded applications返回搜狐,查看更多