mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
ch1-02: 简化,删除cgo/swig/asm版本
This commit is contained in:
parent
96bc1c8ce7
commit
b74615cf5f
@ -285,109 +285,6 @@ func main() {
|
||||
|
||||
Go语言终于移除了语句末尾的分号。这是Go语言在2009年11月10号正式开源之后第一个比较重要的语法改进。从1978年C语言教程第一版引入的分号分割的规则到现在,Go语言的作者们花了整整32年终于移除了语句末尾的分号。在这32年的演化的过程中必然充满了各种八卦故事,我想这一定是Go语言设计者深思熟虑的结果(现在Swift等新的语言也是默认忽略分号的,可见分号确实并不是那么的重要)。
|
||||
|
||||
## CGO版本
|
||||
|
||||
Go语言开源初期就支持通过CGO和C语言保持交互。CGO通过导入一个虚拟的`"C"`包来访问C语言中的函数。下面是CGO版本的“Hello World”程序:
|
||||
|
||||
```go
|
||||
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++的名字修饰规则):
|
||||
|
||||
```c++
|
||||
#include <iostream>
|
||||
|
||||
void SayHello() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
然后创建一个`hello.swigcxx`文件,以SWIG语法导出上面的C++函数`SayHello`:
|
||||
|
||||
```swig
|
||||
%module main
|
||||
|
||||
%inline %{
|
||||
extern void SayHello();
|
||||
%}
|
||||
```
|
||||
|
||||
然后在Go语言中直接访问`SayHello`函数(首字母自动转为大写字母):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
hello "."
|
||||
)
|
||||
|
||||
func main() {
|
||||
hello.SayHello()
|
||||
}
|
||||
```
|
||||
|
||||
需要将上述3个文件放到同一个目录中,并且`hello.swigcxx`和Go文件对应同一个包。系统除了需要安装Go语言环境外,还需要安装对应版本的SWIG工具。最后运行`go build`就可以构建了。
|
||||
|
||||
*注: 在Windows系统下, 路径最长为260个字符. 这个程序生成的中间cgo文件可能导致某些文件的绝对路径长度超出Windows系统限制, 可能导致程序构建失败. 这是由于`go build`调用swig和cgo等命令生成中间文件时生成的不合适的超长文件名导致(作者提交ISSUE3358,Go1.8已经修复)。*
|
||||
|
||||
## Go汇编语言版本
|
||||
|
||||
Go语言底层使用了自己独有的跨操作系统汇编语言,该汇编语言是从Plan9系统的汇编语言演化而来。Go汇编语言并不能独立使用,它是属于Go语言的一个组成部分,必须以Go语言包的方式被组织。下面是Go汇编语言版本的“Hello World”程序:
|
||||
|
||||
先创建一个`main.go`文件,以Go语言的语法声明包和声明汇编语言对应的函数签名,函数签名不能有函数体:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main()
|
||||
```
|
||||
|
||||
然后创建`main_amd64.s`文件,对应Go汇编语言实现AMD64架构的`main`函数:
|
||||
|
||||
```asm
|
||||
#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/ARM/ARM64等)有对应的指令和寄存器,但是汇编语言的基本语法和函数调用规范是一致的,不同操作系统之间用法是一致的。在Go语言标准库中,`runtime`运行时库、`math`数学库和`crypto`密码相关的函数很多是采用汇编语言实现的。其中`runtime`运行时库中采用部分汇编语言并不完全是为了性能,而是运行时的某些特性功能(比如goroutine上下文的切换等)无法用纯Go实现,因此需要汇编代码实现某些辅助功能。对于普通用户而言,Go汇编语言的最大价值在于性能的优化,对于性能比较关键的地方,可以尝试用Go汇编语言实现终极优化。
|
||||
|
||||
|
||||
## 你好, 世界! - V2.0
|
||||
|
||||
|
105
draft/ch1-02.md
Normal file
105
draft/ch1-02.md
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
## CGO版本
|
||||
|
||||
Go语言开源初期就支持通过CGO和C语言保持交互。CGO通过导入一个虚拟的`"C"`包来访问C语言中的函数。下面是CGO版本的“Hello World”程序:
|
||||
|
||||
```go
|
||||
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++的名字修饰规则):
|
||||
|
||||
```c++
|
||||
#include <iostream>
|
||||
|
||||
void SayHello() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
然后创建一个`hello.swigcxx`文件,以SWIG语法导出上面的C++函数`SayHello`:
|
||||
|
||||
```swig
|
||||
%module main
|
||||
|
||||
%inline %{
|
||||
extern void SayHello();
|
||||
%}
|
||||
```
|
||||
|
||||
然后在Go语言中直接访问`SayHello`函数(首字母自动转为大写字母):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
hello "."
|
||||
)
|
||||
|
||||
func main() {
|
||||
hello.SayHello()
|
||||
}
|
||||
```
|
||||
|
||||
需要将上述3个文件放到同一个目录中,并且`hello.swigcxx`和Go文件对应同一个包。系统除了需要安装Go语言环境外,还需要安装对应版本的SWIG工具。最后运行`go build`就可以构建了。
|
||||
|
||||
*注: 在Windows系统下, 路径最长为260个字符. 这个程序生成的中间cgo文件可能导致某些文件的绝对路径长度超出Windows系统限制, 可能导致程序构建失败. 这是由于`go build`调用swig和cgo等命令生成中间文件时生成的不合适的超长文件名导致(作者提交ISSUE3358,Go1.8已经修复)。*
|
||||
|
||||
|
||||
## Go汇编语言版本
|
||||
|
||||
Go语言底层使用了自己独有的跨操作系统汇编语言,该汇编语言是从Plan9系统的汇编语言演化而来。Go汇编语言并不能独立使用,它是属于Go语言的一个组成部分,必须以Go语言包的方式被组织。下面是Go汇编语言版本的“Hello World”程序:
|
||||
|
||||
先创建一个`main.go`文件,以Go语言的语法声明包和声明汇编语言对应的函数签名,函数签名不能有函数体:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main()
|
||||
```
|
||||
|
||||
然后创建`main_amd64.s`文件,对应Go汇编语言实现AMD64架构的`main`函数:
|
||||
|
||||
```asm
|
||||
#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/ARM/ARM64等)有对应的指令和寄存器,但是汇编语言的基本语法和函数调用规范是一致的,不同操作系统之间用法是一致的。在Go语言标准库中,`runtime`运行时库、`math`数学库和`crypto`密码相关的函数很多是采用汇编语言实现的。其中`runtime`运行时库中采用部分汇编语言并不完全是为了性能,而是运行时的某些特性功能(比如goroutine上下文的切换等)无法用纯Go实现,因此需要汇编代码实现某些辅助功能。对于普通用户而言,Go汇编语言的最大价值在于性能的优化,对于性能比较关键的地方,可以尝试用Go汇编语言实现终极优化。
|
Loading…
x
Reference in New Issue
Block a user