gopl-zh.github.com/ch13/ch13.md

3.3 KiB
Raw Blame History

第13章 底层编程

Go语言的设计包含了诸多安全策略限制了可能导致程序运行出错的用法。编译时类型检查可以发现大多数类型不匹配的操作例如两个字符串做减法的错误。字符串、map、slice和chan等所有的内置类型都有严格的类型转换规则。

对于无法静态检测到的错误,例如数组访问越界或使用空指针,运行时动态检测可以保证程序在遇到问题的时候立即终止并打印相关的错误信息。自动内存管理(垃圾内存自动回收)可以消除大部分野指针和内存泄漏相关的问题。

Go语言的实现刻意隐藏了很多底层细节。我们无法知道一个结构体真实的内存布局也无法获取一个运行时函数对应的机器码也无法知道当前的goroutine是运行在哪个操作系统线程之上。事实上Go语言的调度器会自己决定是否需要将某个goroutine从一个操作系统线程转移到另一个操作系统线程。一个指向变量的指针也并没有展示变量真实的地址。因为垃圾回收器可能会根据需要移动变量的内存位置当然变量对应的地址也会被自动更新。

总的来说Go语言的这些特性使得Go程序相比较低级的C语言来说更容易预测和理解程序也不容易崩溃。通过隐藏底层的实现细节也使得Go语言编写的程序具有高度的可移植性因为语言的语义在很大程度上是独立于任何编译器实现、操作系统和CPU系统结构的当然也不是完全绝对独立例如int等类型就依赖于CPU机器字的大小某些表达式求值的具体顺序还有编译器实现的一些额外的限制等

有时候我们可能会放弃使用部分语言特性而优先选择具有更好性能的方法例如需要与其他语言编写的库进行互操作或者用纯Go语言无法实现的某些函数。

在本章我们将展示如何使用unsafe包来摆脱Go语言规则带来的限制讲述如何创建C语言函数库的绑定以及如何进行系统调用。

本章提供的方法不应该轻易使用译注属于黑魔法虽然功能很强大但是也容易误伤到自己。如果没有处理好细节它们可能导致各种不可预测的并且隐晦的错误甚至连有经验的C语言程序员也无法理解这些错误。使用unsafe包的同时也放弃了Go语言保证与未来版本的兼容性的承诺因为它必然会有意无意中使用很多非公开的实现细节而这些实现的细节在未来的Go语言中很可能会被改变。

要注意的是unsafe包是一个采用特殊方式实现的包。虽然它可以和普通包一样的导入和使用但它实际上是由编译器实现的。它提供了一些访问语言内部特性的方法特别是内存布局相关的细节。将这些特性封装到一个独立的包中是为在极少数情况下需要使用的时候同时引起人们的注意译注因为看包的名字就知道使用unsafe包是不安全的。此外有一些环境因为安全的因素可能限制这个包的使用。

不过unsafe包被广泛地用于比较低级的包例如runtime、os、syscall还有net包等因为它们需要和操作系统密切配合但是对于普通的程序一般是不需要使用unsafe包的。