1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-27 14:52:20 +00:00
2018-07-22 18:57:42 +08:00

5.3 KiB
Raw Blame History

CGO版本

Go语言开源初期就支持通过CGO和C语言保持交互。CGO通过导入一个虚拟的"C"包来访问C语言中的函数。下面是CGO版本的“Hello World”程序

package main

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

func main() {
	msg := C.CString("Hello, World!\n")
	defer C.free(unsafe.Pointer(msg))

	C.fputs(msg, C.stdout)
}

先通过C.CString函数将Go语言字符串转为C语言字符串然后调用C语言的C.fputs函数向标准输出窗口打印转换后的C字符串。defer延迟语句保证程序返回前通过C.free释放分配的C字符串。需要注意的是, CGO不支持C语言中的可变参数函数因为Go语言每次函数调用的栈帧大小是固定的而且Go语言中可变参数语法只是切片的一个语法糖而已因此在Go语言中是无法通过CGO访问C语言的printf等可变参数函数的。同时CGO只能访问C语言的函数、变量和简单的宏定义常量CGO并不支持访问C++语言的符号C++和C语言符号的名字修饰规则不同CGO采用C语言的名字修饰规则

其实CGO不仅仅用于在Go语言中调用C语言函数还可以用于导出Go语言函数给C语言函数调用。在用Go语言编写生成C语言的静、动态库时也可以用CGO导出对应的接口函数。正是CGO的存在才保证了Go语言和C语言资源的双向互通同时保证了Go语言可以继承C语言已有的庞大的软件资产。

SWIG版本

Go语言开源初期除了支持通过CGO访问C语言资源外还支持通过SWIG访问C/C++接口。SWIG是从2010年10月04日发布的SWIG-2.0.1版本开始正式支持Go语言的。可以将SWIG看作一个高级的CGO代码自动生成器同时通过生成C语言桥接代码增加了对C++类的支持。下面是SWIG版本的"Hello World"程序:

首先是创建一个hello.cc文件,里面有SayHello函数用于打印(这里的SayHello函数采用C++的名字修饰规则):

#include <iostream>

void SayHello() {
	std::cout << "Hello, World!" << std::endl;
}

然后创建一个hello.swigcxx文件以SWIG语法导出上面的C++函数SayHello:

%module main

%inline %{
extern void SayHello();
%}

然后在Go语言中直接访问SayHello函数(首字母自动转为大写字母):

package main

import (
	hello "."
)

func main() {
	hello.SayHello()
}

需要将上述3个文件放到同一个目录中并且hello.swigcxx和Go文件对应同一个包。系统除了需要安装Go语言环境外还需要安装对应版本的SWIG工具。最后运行go build就可以构建了。

注: 在Windows系统下, 路径最长为260个字符. 这个程序生成的中间cgo文件可能导致某些文件的绝对路径长度超出Windows系统限制, 可能导致程序构建失败. 这是由于go build调用swig和cgo等命令生成中间文件时生成的不合适的超长文件名导致作者提交ISSUE3358Go1.8已经修复)。

Go汇编语言版本

Go语言底层使用了自己独有的跨操作系统汇编语言该汇编语言是从Plan9系统的汇编语言演化而来。Go汇编语言并不能独立使用它是属于Go语言的一个组成部分必须以Go语言包的方式被组织。下面是Go汇编语言版本的“Hello World”程序

先创建一个main.go文件以Go语言的语法声明包和声明汇编语言对应的函数签名函数签名不能有函数体

package main

func main()

然后创建main_amd64.s文件对应Go汇编语言实现AMD64架构的main函数:

#include "textflag.h"
#include "funcdata.h"

// "Hello World!\n"
DATA  text<>+0(SB)/8,$"Hello Wo"
DATA  text<>+8(SB)/8,$"rld!\n"
GLOBL text<>(SB),NOPTR,$16

// func main()
TEXT ·main(SB), $16-0
	NO_LOCAL_POINTERS
	MOVQ $text<>+0(SB), AX
	MOVQ AX, (SP)
	MOVQ $16, 8(SP)
	CALL runtime·printstring(SB)
	RET

代码中#include "textflag.h"语句包含运行时库定义的头文件, 里面含有NOPTR/NO_LOCAL_POINTERS等基本的宏的定义。DATA汇编指令用于定义数据每个数据的宽度必须是1/2/4/8然后GLOBL汇编命令在当前文件内导出text变量符号。TEXT ·main(SB), $16-0用于定义main函数,其中$16-0表示main函数的帧大小是16个字节对应string头的大小用于给runtime·printstring函数传递参数),0表示main函数没有参数和返回值。main函数内部通过调用运行时内部的runtime·printstring(SB)函数来打印字符串。

Go汇编语言虽然针对每种CPU架构主要有386/AMD64/ARMARM64等有对应的指令和寄存器但是汇编语言的基本语法和函数调用规范是一致的不同操作系统之间用法是一致的。在Go语言标准库中runtime运行时库、math数学库和crypto密码相关的函数很多是采用汇编语言实现的。其中runtime运行时库中采用部分汇编语言并不完全是为了性能而是运行时的某些特性功能比如goroutine上下文的切换等无法用纯Go实现因此需要汇编代码实现某些辅助功能。对于普通用户而言Go汇编语言的最大价值在于性能的优化对于性能比较关键的地方可以尝试用Go汇编语言实现终极优化。