第13章 底层编程
Go的设计包含了诸多安全策略, 限制了可能导致程序错误的用法. 编译时类型检查检测可以发现大多数类型不匹配的变量操作, 例如两个字符串做减法的错误. 字符串, 字典, 切片 和管道等所有的内置类型, 都有严格的类型转换规则.
对于无法静态检测到的错误, 例如数组访问越界或使用空指针, 动态检测可以保证程序在遇到问题的时候立即终止并打印相关的错误信息. 自动内存管理(垃圾回收)消除了大部分野指针和内存泄漏的问题.
Go的实现刻意隐藏了很多底层细节. 我们无法知道一个结构体的内存布局, 也无法获取一个运行函数的机器码, 也无法知道当前的 goroutine 是运行在哪个操作系统线程上. 事实上, Go的调度器会自己决定是否需要将 goroutine 从一个操作系统线程转移到另一个操作系统线程. 一个指向变量的指针也并没有展示变量真实的地址. 因为垃圾回收器会根据需要移动变量的位置, 当然对应的也会被自动更新.
总的来说, Go语言的这些特殊使得Go程序相比较低级的C语言来说, 更容易预测, 更容易理解, 也不容易崩溃. 通过隐藏底层的细节, 也使得Go程序具有高度的可移植性, 因为语言的语义在很大程度上是独立于任何编译器, 操作系统和CPU系统结构的(当然也不完全绝对独立: 例如CPU字的大小, 某些表达式求值的顺序, 还有编译器实现的一些限制).
有时候我们可能会放弃部分语言特性而优先选择更好的性能优化, 与其他语言编写的库互操作, 或者不用纯Go语言来实现某些函数.
在本章, 我们将展示如何使用 unsafe 包来摆脱通常的规则限制, 如何创建C函数库的绑定, 以及如何进行系统调用.
本章描述的方法不应该轻易使用. 如果没有处理好细节, 它们可能导致各种不可预测的隐晦的错误, 甚至连本地的C程序员也无法理解. 使用 unsafe 包同时也无法保证与未来版本的兼容性, 因为在有意无意中会使用很多实现的细节, 而这些实现的细节在未来很可能会改变.
unsafe 包的实现比较特殊. 虽然它可以和普通包一样的导入和使用, 但它实际上是由编译器实现的. 它提供了一些访问语言内部特性的方法, 特别是内存布局相关的细节. 将这些特别封装到一个独立的包中, 是为在极少数情况下需要使用的时候, 引起人们的注意(它们是不安全的). 此外, 有一些环境因为安全的因素可能限制这个包的使用.
unsafe 包被广泛地用于比较低级的包, 例如 runtime, os, syscall 还有 net 等, 因为它们需要和操作系统密切配合的, 但是普通的程序一般是不需要的.