This commit is contained in:
Xargin 2016-10-10 14:53:50 +08:00
parent 7d40fb4d28
commit ef1d302ae6
6 changed files with 12 additions and 12 deletions

View File

@ -36,7 +36,7 @@ struct{ float64; int16; bool } // 2 words 3words
struct{ bool; int16; float64 } // 2 words 3words
```
关于内存地址对齐算法的细节超出了本书的范围也不是每一个结构体都需要担心这个问题不过有效的包装可以使数据结构更加紧凑译注未来的Go语言编译器应该会默认优化结构体的顺序当然用于应该也能够指定具体的内存布局,相同讨论请参考 [Issue10014](https://github.com/golang/go/issues/10014) ),内存使用率和性能都可能会受益。
关于内存地址对齐算法的细节超出了本书的范围也不是每一个结构体都需要担心这个问题不过有效的包装可以使数据结构更加紧凑译注未来的Go语言编译器应该会默认优化结构体的顺序当然应该也能够指定具体的内存布局相同讨论请参考 [Issue10014](https://github.com/golang/go/issues/10014) ),内存使用率和性能都可能会受益。
`unsafe.Alignof` 函数返回对应参数的类型需要对齐的倍数. 和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量. 通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.

View File

@ -42,7 +42,7 @@ pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
```
产生错误的原因很微妙。有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动所有的保存变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看一个unsafe.Pointer是一个指向变量的指针因此当变量被移动对应的指针也必须被更新但是uintptr类型的临时变量只是一个普通的数字所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时变量x可能已经被转移这时候临时变量tmp也就不再是现在的`&x.b`地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!
产生错误的原因很微妙。有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动所有的保存变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看一个unsafe.Pointer是一个指向变量的指针因此当变量被移动对应的指针也必须被更新但是uintptr类型的临时变量只是一个普通的数字所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时变量x可能已经被转移这时候临时变量tmp也就不再是现在的`&x.b`地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!
还有很多类似原因导致的错误。例如这条语句:
@ -52,7 +52,7 @@ pT := uintptr(unsafe.Pointer(new(T))) // 提示: 错误!
这里并没有指针引用`new`新创建的变量因此该语句执行完成之后垃圾收集器有权马上回收其内存空间所以返回的pT将是无效的地址。
虽然目前的Go语言实现还没有使用移动GC译注未来可能实现但这不该是编写错误代码侥幸的理由当前的Go语言实现已经有移动变量的场景。在5.2节我们提到goroutine的栈是根据需要动态增长的。当发送栈动态增长的时候,原来栈中的所以变量可能需要被移动到新的更大的栈中,所以我们并不能确保变量的地址在整个使用周期内是不变的。
虽然目前的Go语言实现还没有使用移动GC译注未来可能实现但这不该是编写错误代码侥幸的理由当前的Go语言实现已经有移动变量的场景。在5.2节我们提到goroutine的栈是根据需要动态增长的。当发生栈动态增长的时候,原来栈中的所有变量可能需要被移动到新的更大的栈中,所以我们并不能确保变量的地址在整个使用周期内是不变的。
在编写本文时还没有清晰的原则来指引Go程序员什么样的unsafe.Pointer和uintptr的转换是不安全的参考 [Issue7192](https://github.com/golang/go/issues/7192) . 译注: 该问题已经关闭因此我们强烈建议按照最坏的方式处理。将所有包含变量地址的uintptr类型变量当作BUG处理同时减少不必要的unsafe.Pointer类型到uintptr类型的转换。在第一个例子中有三个转换——字段偏移量到uintptr的转换和转回unsafe.Pointer类型的操作——所有的转换全在一个表达式完成。

View File

@ -105,7 +105,7 @@ fmt.Println(Equal([]string(nil), []string{})) // "true"
fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
```
Equal函数甚至可以处理类似12.3章中导致Display陷入陷入死循环的带有环的数据。
Equal函数甚至可以处理类似12.3章中导致Display陷入死循环的带有环的数据。
```Go
// Circular linked lists a -> b -> a and c -> c.
@ -124,4 +124,4 @@ fmt.Println(Equal(a, c)) // "false"
**练习 13.1** 定义一个深比较函数,对于十亿以内的数字比较,忽略类型差异。
**练习 13.2** 编写一个函数,报告其参数是否循环数据结构。
**练习 13.2** 编写一个函数,报告其参数是否循环数据结构。

View File

@ -12,11 +12,11 @@ func NewWriter(w io.Writer) io.WriteCloser
func NewReader(r io.Reader) (io.ReadCloser, error)
```
bzip2压缩算法是基于优雅的Burrows-Wheeler变换算法运行速度比gzip要慢但是可以提供更高的压缩比。标准库的compress/bzip2包目前还没有提供bzip2压缩算法的实现。完全从头开始实现一个压缩算法是一件繁琐的工作,而且 http://bzip.org 已经有现成的libbzip2的开源实现不仅文档齐全而且性能又好。
bzip2压缩算法是基于优雅的Burrows-Wheeler变换算法运行速度比gzip要慢但是可以提供更高的压缩比。标准库的compress/bzip2包目前还没有提供bzip2压缩算法的实现。完全从头开始实现一个压缩算法是一件繁琐的工作而且 http://bzip.org 已经有现成的libbzip2的开源实现不仅文档齐全而且性能又好。
如果是比较小的C语言库我们完全可以用纯Go语言重新实现一遍。如果我们对性能也没有特殊要求的话我们还可以用os/exec包的方法将C编写的应用程序作为一个子进程运行。只有当你需要使用复杂而且性能更高的底层C接口时就是使用cgo的场景了译注用os/exec包调用子进程的方法会导致程序运行时依赖那个应用程序。下面我们将通过一个例子讲述cgo的具体用法。
译注本章采用的代码都是最新的。因为之前已经出版的书中包含的代码只能在Go1.5之前使用。从Go1.6开始Go语言已经明确规定了哪些Go语言指针可以之间传入C语言函数。新代码重点是增加了bz2alloc和bz2free的两个函数用于bz_stream对象空间的申请和释放操作。下面是新代码中增加的注释说明这个问题
译注本章采用的代码都是最新的。因为之前已经出版的书中包含的代码只能在Go1.5之前使用。从Go1.6开始Go语言已经明确规定了哪些Go语言指针可以直接传入C语言函数。新代码重点是增加了bz2alloc和bz2free的两个函数用于bz_stream对象空间的申请和释放操作。下面是新代码中增加的注释说明这个问题
```Go
// The version of this program that appeared in the first and second
@ -101,9 +101,9 @@ func NewWriter(out io.Writer) io.WriteCloser {
}
```
在预处理过程中cgo工具生成一个临时包用于包含所有在Go语言中访问的C语言的函数或类型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通过以某种特殊的方式调用本地的C编译器来发现在Go源文件导入声明前的注释中包含的C头文件中的内容译注`import "C"`语句前紧挨着的注释是对应cgo的特殊语法对应必要的构建参数选项和C语言代码
在预处理过程中cgo工具生成一个临时包用于包含所有在Go语言中访问的C语言的函数或类型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通过以某种特殊的方式调用本地的C编译器来发现在Go源文件导入声明前的注释中包含的C头文件中的内容译注`import "C"`语句前紧挨着的注释是对应cgo的特殊语法对应必要的构建参数选项和C语言代码
在cgo注释中还可以包含#cgo指令用于给C语言工具链指定特殊的参数。例如CFLAGS和LDFLAGS分别对应传给C语言编译器的编译参数和链接器参数使它们可以特定目录找到bzlib.h头文件和libbz2.a库文件。这个例子假设你已经在/usr目录成功安装了bzip2库。如果bzip2库是安装在不同的位置你需要更新这些参数译注这里有一个从纯C代码生成的cgo绑定不依赖bzip2静态库和操作系统的具体环境具体请访问 https://github.com/chai2010/bzip2 )。
在cgo注释中还可以包含#cgo指令用于给C语言工具链指定特殊的参数。例如CFLAGS和LDFLAGS分别对应传给C语言编译器的编译参数和链接器参数使它们可以特定目录找到bzlib.h头文件和libbz2.a库文件。这个例子假设你已经在/usr目录成功安装了bzip2库。如果bzip2库是安装在不同的位置你需要更新这些参数译注这里有一个从纯C代码生成的cgo绑定不依赖bzip2静态库和操作系统的具体环境具体请访问 https://github.com/chai2010/bzip2 )。
NewWriter函数通过调用C语言的BZ2_bzCompressInit函数来初始化stream中的缓存。在writer结构中还包括了另一个buffer用于输出缓存。

View File

@ -2,9 +2,9 @@
我们在前一章结尾的时候我们警告要谨慎使用reflect包。那些警告同样适用于本章的unsafe包。
高级语言使得程序员不用关心真正运行程序的指令细节,同时也不再需要关注许多如内存布局之类的实现细节。因为高级语言这个绝缘的抽象层,我们可以编写安全健壮的,并且可以运行在不同操作系统上的具有高度可移植性的程序。
高级语言使得程序员不用关心真正运行程序的指令细节,同时也不再需要关注许多如内存布局之类的实现细节。因为高级语言这个绝缘的抽象层,我们可以编写安全健壮的,并且可以运行在不同操作系统上的具有高度可移植性的程序。
但是unsafe包它让程序员可以透过这个绝缘的抽象层直接使用一些必要的功能虽然可能是为了获得更好的性能。但是代价就是牺牲了可移植性和程序安全因此使用unsafe包是一个危险的行为。我们对何时以及如何使用unsafe包的建议和我们在11.5节提到的Knuth对过早优化的建议类似。大多数Go程序员可能永远不会需要直接使用unsafe包。当然也永远都会有一些需要使用unsafe包实现会更简单的场景。如果确实认为使用unsafe包是最理想的方式那么应该尽可能将它限制在较小的范围那样其它代码就忽略unsafe的影响。
但是unsafe包它让程序员可以透过这个绝缘的抽象层直接使用一些必要的功能虽然可能是为了获得更好的性能。但是代价就是牺牲了可移植性和程序安全因此使用unsafe包是一个危险的行为。我们对何时以及如何使用unsafe包的建议和我们在11.5节提到的Knuth对过早优化的建议类似。大多数Go程序员可能永远不会需要直接使用unsafe包。当然也永远都会有一些需要使用unsafe包实现会更简单的场景。如果确实认为使用unsafe包是最理想的方式那么应该尽可能将它限制在较小的范围这样其它代码就可以忽略unsafe的影响。
现在赶紧将最后两章抛入脑后吧。编写一些实实在在的应用是真理。请远离reflect的unsafe包除非你确实需要它们。

View File

@ -12,7 +12,7 @@ Go语言的实现刻意隐藏了很多底层细节。我们无法知道一个结
在本章我们将展示如何使用unsafe包来摆脱Go语言规则带来的限制讲述如何创建C语言函数库的绑定以及如何进行系统调用。
本章提供的方法不应该轻易使用(译注:属于黑魔法,虽然功能很强大,但是也容易误伤到自己)。如果没有处理好细节,它们可能导致各种不可预测的并且隐晦的错误,甚至连有经验的C语言程序员也无法理解这些错误。使用unsafe包的同时也放弃了Go语言保证与未来版本的兼容性的承诺因为它必然会有意无意中使用很多非公开的实现细节而这些实现的细节在未来的Go语言中很可能会被改变。
本章提供的方法不应该轻易使用译注属于黑魔法虽然功能很强大但是也容易误伤到自己。如果没有处理好细节它们可能导致各种不可预测的并且隐晦的错误甚至连有经验的C语言程序员也无法理解这些错误。使用unsafe包的同时也放弃了Go语言保证与未来版本的兼容性的承诺因为它必然会有意无意中使用很多非公开的实现细节而这些实现的细节在未来的Go语言中很可能会被改变。
要注意的是unsafe包是一个采用特殊方式实现的包。虽然它可以和普通包一样的导入和使用但它实际上是由编译器实现的。它提供了一些访问语言内部特性的方法特别是内存布局相关的细节。将这些特性封装到一个独立的包中是为在极少数情况下需要使用的时候同时引起人们的注意译注因为看包的名字就知道使用unsafe包是不安全的。此外有一些环境因为安全的因素可能限制这个包的使用。