回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,33 +1,33 @@
## 13.1. unsafe.Sizeof, Alignof 和 Offsetof
unsafe.Sizeof函數返迴操作數在內存中的字大小,參數可以是任意型的表式,但是它併不會對表達式進行求值。一Sizeof函數調用是一個對應uintptr型的常量表式,因此返迴的結果可以用作數組類型的度大小,或者用作算其他的常量。
unsafe.Sizeof函数返回操作数在内存中的字大小,参数可以是任意型的表式,但是它并不会对表达式进行求值。一Sizeof函数调用是一个对应uintptr型的常量表式,因此返回的结果可以用作数组类型的度大小,或者用作算其他的常量。
```Go
import "unsafe"
fmt.Println(unsafe.Sizeof(float64(0))) // "8"
```
Sizeof函數返迴的大小包括數據結構中固定的部分,例如字符串對應結構體中的指和字符串度部分,但是不包含指指向的字符串的容。Go言中非聚合型通常有一固定的大小,管在不同工具下生成的實際大小可能有所不同。考到可移植性,引用型或包含引用型的大小在32位平台上是4個字節在64位平台上是8個字節
Sizeof函数返回的大小包括数据结构中固定的部分,例如字符串对应结构体中的指和字符串度部分,但是不包含指指向的字符串的容。Go言中非聚合型通常有一固定的大小,管在不同工具下生成的实际大小可能有所不同。考到可移植性,引用型或包含引用型的大小在32位平台上是4个字节在64位平台上是8个字节
計算機在加和保存數據時,如果存地址合理地對齊的將會更有效率。例如2字大小的int16型的量地址應該是偶,一4字大小的rune類型變量的地址應該是4的倍,一8字大小的float64、uint64或64-bit指針類型變量的地址應該是8字節對齊的。但是對於再大的地址對齊倍數則是不需要的,使是complex128等大的數據類型最多也是8字節對齊
计算机在加和保存数据时,如果存地址合理地对齐的将会更有效率。例如2字大小的int16型的量地址应该是偶,一4字大小的rune类型变量的地址应该是4的倍,一8字大小的float64、uint64或64-bit指针类型变量的地址应该是8字节对齐的。但是对于再大的地址对齐倍数则是不需要的,使是complex128等大的数据类型最多也是8字节对齐
地址對齊這個因素,一聚合型(結構體或數組)的大小至少是所有字段或元素大小的和,或者更大因可能存在存空洞。存空洞是編譯器自添加的有被使用的存空,用於保證後面每字段或元素的地址相對於結構或數組的開始地址能合理地對齊(譯註:內存空洞可能存在一些隨機數據,可能會對用unsafe包直接操作存的處理産生影)。
地址对齐这个因素,一聚合型(结构体或数组)的大小至少是所有字段或元素大小的和,或者更大因可能存在存空洞。存空洞是编译器自添加的有被使用的存空,用于保证后面每字段或元素的地址相对于结构或数组的开始地址能合理地对齐(译注:内存空洞可能存在一些随机数据,可能会对用unsafe包直接操作存的处理产生影)。
型 | 大小
型 | 大小
----------------------------- | ----
bool | 1個字節
intN, uintN, floatN, complexN | N/8個字節(例如float64是8個字節)
int, uint, uintptr | 1個機器字
*T | 1個機器字
string | 2個機器字(data,len)
[]T | 3個機器字(data,len,cap)
map | 1個機器字
func | 1個機器字
chan | 1個機器字
interface | 2個機器字(type,value)
bool | 1个字节
intN, uintN, floatN, complexN | N/8个字节(例如float64是8个字节)
int, uint, uintptr | 1个机器字
*T | 1个机器字
string | 2个机器字(data,len)
[]T | 3个机器字(data,len,cap)
map | 1个机器字
func | 1个机器字
chan | 1个机器字
interface | 2个机器字(type,value)
Go言的規范併沒有要求一字段的聲明順序和存中的序是一致的,所以理上一個編譯器可以意地重新排列每字段的存位置,然在作本書的時候編譯器還沒有這麽做。下面的三個結構體雖然有着相同的字段,但是第一種寫法比另外的兩個需要多50%的存。
Go言的规范并没有要求一字段的声明顺序和存中的序是一致的,所以理上一个编译器可以意地重新排列每字段的存位置,然在作本书的时候编译器还没有这么做。下面的三个结构体虽然有着相同的字段,但是第一种写法比另外的两个需要多50%的存。
```Go
// 64-bit 32-bit
@@ -36,13 +36,13 @@ 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個字節), 其它的類型對齊到機器字大小.
`unsafe.Alignof`数返回对应参数的类型需要对齐的倍. 和 Sizeof 似, Alignof 也是返回一个常量表式, 对应一个常量. 通常情下布尔和数字类型需要对齐到它本身的大小(最多8个字节), 其它的类型对齐到机器字大小.
`unsafe.Offsetof`數的參數必須是一字段 `x.f`, 然後返迴 `f` 字段相對於 `x` 起始地址的偏移量, 包括可能的空洞.
`unsafe.Offsetof`数的参数必须是一字段 `x.f`, 然后返回 `f` 字段相对于 `x` 起始地址的偏移量, 包括可能的空洞.
13.1 示了一個結構體變量 x 以及其在32位和64位器上的典型的存. 灰色域是空洞.
13.1 示了一个结构体变量 x 以及其在32位和64位器上的典型的存. 灰色域是空洞.
```Go
var x struct {
@@ -52,11 +52,11 @@ var x struct {
}
```
下面示了x和它的三字段調用unsafe包相關函數的計算結果:
下面示了x和它的三字段用unsafe包相关函数的计算结果:
![](../images/ch13-01.png)
32位繫統
32位系统
```
Sizeof(x) = 16 Alignof(x) = 4
@@ -65,7 +65,7 @@ Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 12 Alignof(x.c) = 4 Offsetof(x.c) = 4
```
64位繫統
64位系统
```
Sizeof(x) = 32 Alignof(x) = 8
@@ -74,5 +74,5 @@ Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 24 Alignof(x.c) = 8 Offsetof(x.c) = 8
```
雖然這幾個函數在不安全的unsafe包但是這幾個函數調用併不是的不安全,特别在需要優化內存空間時它們返迴的結果對於理解原生的存布局很有助。
虽然这几个函数在不安全的unsafe包但是这几个函数调用并不是的不安全,特别在需要优化内存空间时它们返回的结果对于理解原生的存布局很有助。

View File

@@ -1,8 +1,8 @@
## 13.2. unsafe.Pointer
大多數指針類型會寫`*T`,表示是“一指向T類型變量的指”。unsafe.Pointer是特别定的一種指針類型(譯註:類似C言中的`void*`型的指),它可以包含任意類型變量的地址。然,我不可以直接通`*p`來獲取unsafe.Pointer指指向的眞實變量的值,因爲我們併不知道量的具體類型。和普通指針一樣unsafe.Pointer指也是可以比的,且支持和nil常量比較判斷是否空指
大多数指针类型会写`*T`,表示是“一指向T类型变量的指”。unsafe.Pointer是特别定的一种指针类型(译注:类似C言中的`void*`型的指),它可以包含任意类型变量的地址。然,我不可以直接通`*p`来获取unsafe.Pointer指指向的真实变量的值,因为我们并不知道量的具体类型。和普通指针一样unsafe.Pointer指也是可以比的,且支持和nil常量比较判断是否空指
普通的`*T`型指可以被轉化爲unsafe.Pointer型指針,併且一unsafe.Pointer型指也可以被轉迴普通的指,被轉迴普通的指針類型併不需要和原始的`*T`型相同。通過將`*float64`型指針轉化爲`*uint64`型指,我可以看一個浮點數變量的位模式。
普通的`*T`型指可以被转化为unsafe.Pointer型指针,并且一unsafe.Pointer型指也可以被转回普通的指,被转回普通的指针类型并不需要和原始的`*T`型相同。通过将`*float64`型指针转化为`*uint64`型指,我可以看一个浮点数变量的位模式。
```Go
package math
@@ -12,11 +12,11 @@ func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
```
過轉爲新類型指,我可以更新浮點數的位模式。通位模式操作浮點數是可以的,但是更重要的意是指針轉換語法讓我們可以在不破壞類型繫統的前提下向內存寫入任意的值。
过转为新类型指,我可以更新浮点数的位模式。通位模式操作浮点数是可以的,但是更重要的意是指针转换语法让我们可以在不破坏类型系统的前提下向内存写入任意的值。
unsafe.Pointer指也可以被轉化爲uintptr型,然保存到指針型數值變量中(譯註:這隻是和前指相同的一個數字值,不是一個指針),然用以做必要的指針數值運算。(第三章uintptr是一個無符號的整型,足以保存一地址)這種轉換雖然也是可逆的,但是uintptr轉爲unsafe.Pointer指可能會破壞類型繫統,因爲併不是所有的字都是有效的存地址。
unsafe.Pointer指也可以被转化为uintptr型,然保存到指针型数值变量中(译注:这只是和前指相同的一个数字值,不是一个指针),然用以做必要的指针数值运算。(第三章uintptr是一个无符号的整型,足以保存一地址)这种转换虽然也是可逆的,但是uintptr转为unsafe.Pointer指可能会破坏类型系统,因为并不是所有的字都是有效的存地址。
許多將unsafe.Pointer指針轉爲原生字,然後再轉迴爲unsafe.Pointer型指的操作也是不安全的。比如下面的例子需要將變量x的地址加上b字段地址偏移量轉化爲`*int16`型指,然後通過該指針更新x.b
许多将unsafe.Pointer指针转为原生字,然后再转回为unsafe.Pointer型指的操作也是不安全的。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为`*int16`型指,然后通过该指针更新x.b
<u><i>gopl.io/ch13/unsafeptr</i></u>
```Go
@@ -26,14 +26,14 @@ var x struct {
c []int
}
// 和 pb := &x.b 等
// 和 pb := &x.b 等
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
fmt.Println(x.b) // "42"
```
上面的寫法盡管很繁,但在這里併不是一件事,因爲這些功能應該很謹慎地使用。不要試圖引入一uintptr型的臨時變量,因它可能會破壞代碼的安全性(譯註:這是眞正可以體會unsafe包何不安全的例子)。下面段代碼是錯誤的:
上面的写法尽管很繁,但在这里并不是一件事,因为这些功能应该很谨慎地使用。不要试图引入一uintptr型的临时变量,因它可能会破坏代码的安全性(译注:这是真正可以体会unsafe包何不安全的例子)。下面段代码是错误的:
```Go
// NOTE: subtly incorrect!
@@ -42,21 +42,21 @@ 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`地址。第三向之前效地址空间的赋值语句将彻底摧毁整个程序!
有很多似原因致的錯誤。例如這條語句:
有很多似原因致的错误。例如这条语句:
```Go
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯誤!
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 错误!
```
這里併沒有指引用`new`建的量,因此該語句執行完成之,垃圾收集器有權馬上迴收其存空,所以返的pT將是無效的地址。
这里并没有指引用`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型的操作——所有的轉換全在一個表達式完成。
编写本文时,还没有清晰的原则来指引Go程序,什么样的unsafe.Pointer和uintptr的转换是不安全的(考 [Issue7192](https://github.com/golang/go/issues/7192) . 译注: 该问题已经关闭),因此我们强烈建按照最的方式理。所有包含量地址的uintptr类型变量当作BUG理,同时减少不必要的unsafe.Pointer型到uintptr型的转换。在第一例子中,有三个转换——字段偏移量到uintptr的转换和转回unsafe.Pointer型的操作——所有的转换全在一个表达式完成。
當調用一個庫函數,併且返的是uintptr型地址時(譯註:普通方法實現的函數不盡量不要返迴該類型。下面例子是reflect包的函reflect包和unsafe包一都是采用特殊技術實現的,編譯器可能給它們開了後門),比如下面反射包中的相關函數,返迴的結果應該立卽轉換爲unsafe.Pointer以保指指向的是相同的量。
当调用一个库函数,并且返的是uintptr型地址时(译注:普通方法实现的函数不尽量不要返回该类型。下面例子是reflect包的函reflect包和unsafe包一都是采用特殊技术实现的,编译器可能给它们开了后门),比如下面反射包中的相关函数,返回的结果应该立即转换为unsafe.Pointer以保指指向的是相同的量。
```Go
package reflect

View File

@@ -1,6 +1,6 @@
## 13.3. 示例: 深度相等判
## 13.3. 示例: 深度相等判
自reflect包的DeepEqual函可以對兩個值進行深度相等判。DeepEqual函使用建的==比操作符對基礎類型進行相等判斷,對於複合類型則遞歸該變量的每個基礎類型然後做類似的比較判斷。因它可以工作在任意的型上,甚至對於一些不支持==操作算符的型也可以工作,因此在一些測試代碼中廣泛地使用該函數。比如下面的代是用DeepEqual函數比較兩個字符串數組是否相等。
自reflect包的DeepEqual函可以对两个值进行深度相等判。DeepEqual函使用建的==比操作符对基础类型进行相等判断,对于复合类型则递归该变量的每个基础类型然后做类似的比较判断。因它可以工作在任意的型上,甚至对于一些不支持==操作算符的型也可以工作,因此在一些测试代码中广泛地使用该函数。比如下面的代是用DeepEqual函数比较两个字符串数组是否相等。
```Go
func TestSplit(t *testing.T) {
@@ -10,7 +10,7 @@ func TestSplit(t *testing.T) {
}
```
管DeepEqual函很方便,而且可以支持任意的數據類型,但是它也有不足之。例如,它將一個nil值的map和非nil值但是空的map作不相等,同nil值的slice 和非nil但是空的slice也作不相等。
管DeepEqual函很方便,而且可以支持任意的数据类型,但是它也有不足之。例如,它将一个nil值的map和非nil值但是空的map作不相等,同nil值的slice 和非nil但是空的slice也作不相等。
```Go
var a, b []string = nil, []string{}
@@ -20,7 +20,7 @@ var c, d map[string]int = nil, make(map[string]int)
fmt.Println(reflect.DeepEqual(c, d)) // "false"
```
希望在這里實現一個自己的Equal函,用於比較類型的值。和DeepEqual函數類似的地方是它也是基slice和map的每元素進行遞歸比較,不同之是它nil值的slicemap和非nil值但是空的slice作相等的值。基部分的比可以基reflect包完成和12.3章的Display函數的實現方法似。同,我也定了一個內部函equal於內部的遞歸比較。讀者目前不用心seen參數的具體含義。對於每一需要比的x和yequal函首先檢測它們是否都有效(或都效),然後檢測它們是否是相同的型。剩下的部分是一個鉅大的switch分支相同基礎類型的元素比。因爲頁面空的限,我省略了一些相似的分支。
希望在这里实现一个自己的Equal函,用于比较类型的值。和DeepEqual函数类似的地方是它也是基slice和map的每元素进行递归比较,不同之是它nil值的slicemap和非nil值但是空的slice作相等的值。基部分的比可以基reflect包完成和12.3章的Display函数的实现方法似。同,我也定了一个内部函equal于内部的递归比较。读者目前不用心seen参数的具体含义。对于每一需要比的x和yequal函首先检测它们是否都有效(或都效),然后检测它们是否是相同的型。剩下的部分是一个巨大的switch分支相同基础类型的元素比。因为页面空的限,我省略了一些相似的分支。
<u><i>gopl.io/ch13/equal</i></u>
```Go
@@ -63,7 +63,7 @@ func equal(x, y reflect.Value, seen map[comparison]bool) bool {
}
```
和前面的建議一樣,我們併不公reflect包相的接口,所以出的函需要在部自己將變量轉爲reflect.Value型。
和前面的建议一样,我们并不公reflect包相的接口,所以出的函需要在部自己将变量转为reflect.Value型。
```Go
// Equal reports whether x and y are deeply equal.
@@ -78,7 +78,7 @@ type comparison struct {
}
```
爲了確保算法對於有環的數據結構也能正常退出,我們必須記録每次已經比較的變量,而避免入第二次的比。Equal函分配了一組用於比較的結構體,包含每對比較對象的地址unsafe.Pointer形式保存型。我們要記録類型的原因是,有些不同的量可能對應相同的地址。例如如果x和y都是數組類型,那x和x[0]將對應相同的地址y和y[0]也是對應相同的地址,可以用於區分xy之的比或x[0]y[0]之的比是否進行過了。
为了确保算法对于有环的数据结构也能正常退出,我们必须记录每次已经比较的变量,而避免入第二次的比。Equal函分配了一组用于比较的结构体,包含每对比较对象的地址unsafe.Pointer形式保存型。我们要记录类型的原因是,有些不同的量可能对应相同的地址。例如如果x和y都是数组类型,那x和x[0]将对应相同的地址y和y[0]也是对应相同的地址,可以用于区分xy之的比或x[0]y[0]之的比是否进行过了。
```Go
// cycle check
@@ -96,7 +96,7 @@ if x.CanAddr() && y.CanAddr() {
}
```
是Equal函用法的例子:
是Equal函用法的例子:
```Go
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true"
@@ -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.
@@ -122,6 +122,6 @@ fmt.Println(Equal(a, b)) // "false"
fmt.Println(Equal(a, c)) // "false"
```
**練習 13.1**義一個深比較函數,對於十億以內的數字比,忽略型差
**练习 13.1**义一个深比较函数,对于十亿以内的数字比,忽略型差
**練習 13.2** 編寫一個函數,報告其參數是否循環數據結構
**练习 13.2** 编写一个函数,报告其参数是否循环数据结构

View File

@@ -1,10 +1,10 @@
## 13.4. 通cgo調用C代
## 13.4. 通cgo用C代
Go程序可能遇到要訪問C語言的某些硬件驅動函數的場景或者是從一個C++語言實現的嵌入式數據庫査詢記録的場或者是使用Fortran語言實現的一些性代數庫的場景。C言作爲一個通用言,很多庫會選擇提供一C兼容的API用其他不同的編程語言實現(譯Go言需要也應該擁抱這些鉅大的代碼遺産)。
Go程序可能遇到要访问C语言的某些硬件驱动函数的场景或者是从一个C++语言实现的嵌入式数据库查询记录的场或者是使用Fortran语言实现的一些线性代数库的场景。C言作为一个通用言,很多库会选择提供一C兼容的API用其他不同的编程语言实现(译Go言需要也应该拥抱这些巨大的代码遗产)。
在本中,我們將構建一個簡易的數據壓縮程序,使用了一Go言自的叫cgo的用支援C言函數調用的工具。這類工具一般被稱爲 *foreign-function interfaces* 簡稱ffi, 且在似工具中cgo也不是唯一的。SWIG http://swig.org )是另一個類似的且被泛使用的工具SWIG提供了很多複雜特性以支援C++的特性但SWIG不是我們要討論的主
在本中,我们将构建一个简易的数据压缩程序,使用了一Go言自的叫cgo的用支援C言函数调用的工具。这类工具一般被称为 *foreign-function interfaces* 简称ffi, 且在似工具中cgo也不是唯一的。SWIG http://swig.org )是另一个类似的且被广泛使用的工具SWIG提供了很多复杂特性以支援C++的特性但SWIG不是我们要讨论的主
標準庫`compress/...`子包有很多流行的壓縮算法的編碼和解碼實現包括流行的LZW壓縮算法Unix的compress命令用的算法和DEFLATE壓縮算法GNU gzip命令用的算法些包的API的細節雖然有些差,但是它都提供了針對 io.Writer類型輸出的壓縮接口和提供了針對io.Reader類型輸入的解壓縮接口。例如:
标准库`compress/...`子包有很多流行的压缩算法的编码和解码实现包括流行的LZW压缩算法Unix的compress命令用的算法和DEFLATE压缩算法GNU gzip命令用的算法些包的API的细节虽然有些差,但是它都提供了针对 io.Writer类型输出的压缩接口和提供了针对io.Reader类型输入的解压缩接口。例如:
```Go
package gzip // compress/gzip
@@ -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的具用法。
如果是比小的C语言库,我完全可以用Go言重新实现一遍。如果我们对性能也有特殊要求的,我们还可以用os/exec包的方法将C编写的应用程序作为一个子进程运行。只有当你需要使用复杂而且性能更高的底C接口就是使用cgo的景了(译注用os/exec包用子程的方法会导致程序运行时依赖那个应用程序)。下面我们将通过一个例子述cgo的具用法。
譯註:本章采用的代都是最新的。因之前已出版的中包含的代碼隻能在Go1.5之前使用。Go1.6Go言已經明確規定了哪些Go言指可以之間傳入C言函。新代碼重點是增加了bz2alloc和bz2free的兩個函數,用bz_stream象空的申請和釋放操作。下面是新代中增加的註釋,説明這個問題
译注:本章采用的代都是最新的。因之前已出版的中包含的代码只能在Go1.5之前使用。Go1.6Go言已经明确规定了哪些Go言指可以之间传入C言函。新代码重点是增加了bz2alloc和bz2free的两个函数,用bz_stream象空的申请和释放操作。下面是新代中增加的注释,说明这个问题
```Go
// The version of this program that appeared in the first and second
@@ -37,9 +37,9 @@ bzip2壓縮算法是基於優雅的Burrows-Wheeler變換算法運行速度
// pointers to Go variables.
```
要使用libbzip2需要先建一bz_stream結構體,用保持入和輸出緩存。然有三個函數BZ2_bzCompressInit用初始化BZ2_bzCompress用於將輸入緩存的數據壓縮到輸出緩BZ2_bzCompressEnd用於釋放不需要的存。(目前不要心包的具體結構, 這個例子的目的就是演示各部分如何合在一起的。)
要使用libbzip2需要先建一bz_stream结构体,用保持入和输出缓存。然有三个函数BZ2_bzCompressInit用初始化BZ2_bzCompress用于将输入缓存的数据压缩到输出缓BZ2_bzCompressEnd用于释放不需要的存。(目前不要心包的具体结构, 这个例子的目的就是演示各部分如何合在一起的。)
可以在Go代中直接調用BZ2_bzCompressInit和BZ2_bzCompressEnd但是對於BZ2_bzCompress們將定義一個C語言的包裝函數,用它完成正的工作。下面是C代碼,對應一個獨立的文件。
可以在Go代中直接用BZ2_bzCompressInit和BZ2_bzCompressEnd但是对于BZ2_bzCompress们将定义一个C语言的包装函数,用它完成正的工作。下面是C代码,对应一个独立的文件。
<u><i>gopl.io/ch13/bzip</i></u>
```C
@@ -61,7 +61,7 @@ int bz2compress(bz_stream *s, int action,
}
```
現在讓我們轉到Go言部分,第一部分如下所示。其中`import "C"`句是比特别的。其實併沒有一叫C的包但是這行語句會讓Go編譯程序在編譯之前先行cgo工具。
现在让我们转到Go言部分,第一部分如下所示。其中`import "C"`句是比特别的。其实并没有一叫C的包但是这行语句会让Go编译程序在编译之前先行cgo工具。
```Go
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
@@ -101,13 +101,13 @@ 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於輸出緩存。
NewWriter函数通过调用C言的BZ2_bzCompressInit函数来初始化stream中的存。在writer结构中还包括了另一buffer于输出缓存。
下面是Write方法的實現,返成功壓縮數據的大小,主是一個循環中調用C言的bz2compress函數實現的。從代碼可以看到Go程序可以訪問C語言的bz_stream、char和uint型,可以訪問bz2compress等函,甚至可以訪問C語言中像BZ_RUN那的宏定全部都是以C.x語法訪問。其中C.uint型和Go言的uint類型併不相同,使它具有相同的大小也是不同的型。
下面是Write方法的实现,返成功压缩数据的大小,主是一个循环中调用C言的bz2compress函数实现的。从代码可以看到Go程序可以访问C语言的bz_stream、char和uint型,可以访问bz2compress等函,甚至可以访问C语言中像BZ_RUN那的宏定全部都是以C.x语法访问。其中C.uint型和Go言的uint类型并不相同,使它具有相同的大小也是不同的型。
```Go
func (w *writer) Write(data []byte) (int, error) {
@@ -131,9 +131,9 @@ func (w *writer) Write(data []byte) (int, error) {
}
```
在循的每次迭代中向bz2compress傳入數據的地址和剩部分的度,還有輸出緩存w.outbuf的地址和容量。這兩個長度信息通過它們的地址入而不是值入,因bz2compress函可能會根據已經壓縮的數據和壓縮後數據的大小更新這兩個值。每個塊壓縮後的數據被寫入到底的io.Writer。
在循的每次迭代中向bz2compress传入数据的地址和剩部分的度,还有输出缓存w.outbuf的地址和容量。这两个长度信息通过它们的地址入而不是值入,因bz2compress函可能会根据已经压缩的数据和压缩后数据的大小更新这两个值。每个块压缩后的数据被写入到底的io.Writer。
Close方法和Write方法有着似的結構,通過一個循環將剩餘的壓縮數據刷新到輸出緩存。
Close方法和Write方法有着似的结构,通过一个循环将剩余的压缩数据刷新到输出缓存。
```Go
// Close flushes the compressed data and closes the stream.
@@ -161,11 +161,11 @@ func (w *writer) Close() error {
}
```
壓縮完成Close方法用了defer函數確保函退出前調用C.BZ2_bzCompressEnd和C.bz2free放相的C語言運行時資源。此刻w.stream指針將不再有效,我們將它設置爲nil以保安全,然在每方法中增加了nil檢測,以防止用戶在關閉後依然錯誤使用相方法。
压缩完成Close方法用了defer函数确保函退出前用C.BZ2_bzCompressEnd和C.bz2free放相的C语言运行时资源。此刻w.stream指针将不再有效,我们将它设置为nil以保安全,然在每方法中增加了nil检测,以防止用户在关闭后依然错误使用相方法。
上面的實現中,不僅僅寫是非併發安全的,甚至併發調用Close和Write方法也可能致程序的的崩潰。脩複這個問題是練習13.3的容。
上面的实现中,不仅仅写是非并发安全的,甚至并发调用Close和Write方法也可能致程序的的崩溃。修复这个问题是练习13.3的容。
下面的bzipper程序使用我自己包實現的bzip2壓縮命令。它的行爲和許多Unix繫統的bzip2命令似。
下面的bzipper程序使用我自己包实现的bzip2压缩命令。它的行为和许多Unix系统的bzip2命令似。
<u><i>gopl.io/ch13/bzipper</i></u>
```Go
@@ -190,7 +190,7 @@ func main() {
}
```
在上面的景中,我使用bzipper壓縮了/usr/share/dict/words繫統自帶的詞典,938,848字節壓縮到335,405字。大是原始數據大小的三分之一。然使用繫統自帶的bunzip2命令行解壓。壓縮前後文件的SHA256哈希是相同了,這也説明了我們的壓縮工具是正的。(如果你的繫統沒有sha256sum命令麽請先按照練習4.2實現一個類似的工具)
在上面的景中,我使用bzipper压缩了/usr/share/dict/words系统自带的词典,938,848字节压缩到335,405字。大是原始数据大小的三分之一。然使用系统自带的bunzip2命令行解压。压缩前后文件的SHA256哈希是相同了,这也说明了我们的压缩工具是正的。(如果你的系统没有sha256sum命令么请先按照练习4.2实现一个类似的工具)
```
$ go build gopl.io/ch13/bzipper
@@ -204,8 +204,8 @@ $ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum
126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed -
```
演示了如何將一個C語言庫鏈接到Go言程序。相反, Go編譯爲靜態庫然後鏈接到C程序或者Go程序編譯爲動態庫然後在C程序中動態加載也都是可行的(譯註在Go1.5中Windows繫統的Go語言實現併不支持生成C語言動態庫或靜態庫的特性。不好消息是,目前已有人在嚐試解決這個問題,具體請訪問 [Issue11058](https://github.com/golang/go/issues/11058) )。里我們隻展示的cgo很小的一些方面更多的關於內存管理、指針、迴調函數、中斷信號處理、字符串、errno理、終結以及goroutines和繫統線程的關繫等,有很多細節可以討論。特别是如何Go言的指針傳入C函數的規則也是異常複雜的(譯註:簡單來説,要入C函的Go指指向的數據本身不能包含指或其他引用型;且C函在返迴後不能繼續持有Go指針;併且在C函數返迴之前Go指是被定的,不能導致對應指針數據被移動或棧的調部分的原因在13.2節有討論但是在Go1.5中還沒有被明確(譯註Go1.6將會明確cgo中的指使用規則)。如果要一步閲讀,可以 https://golang.org/cmd/cgo 始。
演示了如何将一个C语言库链接到Go言程序。相反, Go编译为静态库然后链接到C程序或者Go程序编译为动态库然后在C程序中动态加载也都是可行的(译注在Go1.5中Windows系统的Go语言实现并不支持生成C语言动态库或静态库的特性。不好消息是,目前已有人在尝试解决这个问题,具体请访问 [Issue11058](https://github.com/golang/go/issues/11058) )。里我们只展示的cgo很小的一些方面更多的关于内存管理、指针、回调函数、中断信号处理、字符串、errno理、终结以及goroutines和系统线程的关系等,有很多细节可以讨论。特别是如何Go言的指针传入C函数的规则也是异常复杂的(译注:简单来说,要入C函的Go指指向的数据本身不能包含指或其他引用型;且C函在返回后不能继续持有Go指针;并且在C函数返回之前Go指是被定的,不能导致对应指针数据被移动或栈的调部分的原因在13.2节有讨论但是在Go1.5中还没有被明确(译注Go1.6将会明确cgo中的指使用规则)。如果要一步阅读,可以 https://golang.org/cmd/cgo 始。
**練習 13.3** 使用sync.Mutex以保bzip2.writer在多goroutines中被併發調用是安全的。
**练习 13.3** 使用sync.Mutex以保bzip2.writer在多goroutines中被并发调用是安全的。
**練習 13.4**爲C庫依賴的限。 使用os/exec包啟動/bin/bzip2命令作爲一個子進程,提供一個純Go的bzip.NewWriter的替代實現(譯註:雖然是Go實現,但是運行時將依賴/bin/bzip2命令其他操作繫統可能無法運行)。
**练习 13.4**为C库依赖的限。 使用os/exec包启动/bin/bzip2命令作为一个子进程,提供一个纯Go的bzip.NewWriter的替代实现(译注:虽然是Go实现,但是运行时将依赖/bin/bzip2命令其他操作系统可能无法运行)。

View File

@@ -1,12 +1,12 @@
## 13.5. 幾點忠告
## 13.5. 几点忠告
在前一章尾的候,我警告要慎使用reflect包。那些警告同樣適用於本章的unsafe包。
在前一章尾的候,我警告要慎使用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包除非你確實需要它
在,赶紧将最后两章抛入脑后吧。编写一些实实在在的用是理。请远离reflect的unsafe包除非你确实需要它
用Go快樂地編程。我希望你能像我們一樣喜歡Go言。
用Go快乐地编程。我希望你能像我们一样喜欢Go言。

View File

@@ -1,20 +1,20 @@
# 第13章 底層編
# 第13章 底层编
Go言的設計包含了多安全策略,限了可能致程序行出現錯誤的用法。編譯時類型檢査檢査可以發現大多數類型不匹配的操作,例如兩個字符串做法的錯誤。字符串、map、slice和chan等所有的內置類型,都有格的類型轉換規則
Go言的设计包含了多安全策略,限了可能致程序行出现错误的用法。编译时类型检查检查可以发现大多数类型不匹配的操作,例如两个字符串做法的错误。字符串、map、slice和chan等所有的内置类型,都有格的类型转换规则
對於無法靜態檢測到的錯誤,例如數組訪問越界或使用空指針,運行時動態檢測可以保程序在遇到問題的時候立卽終止併打印相關的錯誤信息。自動內存管理(垃圾存自動迴收)可以消除大部分野指針和內存洩漏相關的問題
对于无法静态检测到的错误,例如数组访问越界或使用空指针,运行时动态检测可以保程序在遇到问题的时候立即终止并打印相关的错误信息。自动内存管理(垃圾存自动回收)可以消除大部分野指针和内存泄漏相关的问题
Go言的實現刻意藏了很多底層細節。我們無法知道一個結構體眞實的內存布局,也無法獲取一個運行時函數對應的機器碼,也法知道前的goroutine是行在哪操作繫統線程之上。事Go言的調度器自己定是否需要將某個goroutine從一個操作繫統線程轉移到另一操作繫統線程。一指向量的指針也併沒有展示變量眞實的地址。因垃圾收器可能會根據需要移動變量的存位置,當然變量對應的地址也被自更新。
Go言的实现刻意藏了很多底层细节。我们无法知道一个结构体真实的内存布局,也无法获取一个运行时函数对应的机器码,也法知道前的goroutine是行在哪操作系统线程之上。事Go言的度器自己定是否需要将某个goroutine从一个操作系统线程转移到另一操作系统线程。一指向量的指针也并没有展示变量真实的地址。因垃圾收器可能会根据需要移动变量的存位置,当然变量对应的地址也被自更新。
總的來説Go言的些特性使得Go程序相比較低級的C語言來説更容易預測和理解,程序也不容易崩。通過隱藏底層的實現細節也使得Go語言編寫的程序具有高度的可移植性,因爲語言的語義在很大程度上是獨立於任何編譯器實現、操作繫統和CPU繫統結構的(然也不是完全絶對獨例如int等型就依賴於CPU器字的大小,某些表式求值的具體順序,還有編譯器實現的一些外的限等)。
总的来说Go言的些特性使得Go程序相比较低级的C语言来说更容易预测和理解,程序也不容易崩。通过隐藏底层的实现细节也使得Go语言编写的程序具有高度的可移植性,因为语言的语义在很大程度上是独立于任何编译器实现、操作系统和CPU系统结构的(然也不是完全绝对独例如int等型就依赖于CPU器字的大小,某些表式求值的具体顺序,还有编译器实现的一些外的限等)。
候我可能會放棄使用部分言特性而優先選擇更好具有更好性能的方法,例如需要其他語言編寫的庫互操作,或者用Go語言無法實現的某些函
候我可能会放弃使用部分言特性而优先选择更好具有更好性能的方法,例如需要其他语言编写的库互操作,或者用Go语言无法实现的某些函
在本章,我們將展示如何使用unsafe包來襬脫Go語言規則帶來的限製述如何建C言函數庫的綁定,以及如何進行繫統調用。
在本章,我们将展示如何使用unsafe包来摆脱Go语言规则带来的限制述如何建C言函数库的绑定,以及如何进行系统调用。
本章提供的方法不應該輕易使用(譯註:屬於黑魔法,然可能功能很大,但是也容易誤傷到自己)。如果沒有處理好細節,它可能致各不可預測的併且隱晦的錯誤,甚至連有經驗的的C言程序員也無法理解這些錯誤。使用unsafe包的同也放了Go言保證與未來版本的兼容性的承,因它必然在有意意中使用很多實現的細節,而這些實現的細節在未的Go言中很可能被改
本章提供的方法不应该轻易使用(译注:属于黑魔法,然可能功能很大,但是也容易误伤到自己)。如果没有处理好细节,它可能致各不可预测的并且隐晦的错误,甚至连有经验的的C言程序员也无法理解这些错误。使用unsafe包的同也放了Go言保证与未来版本的兼容性的承,因它必然在有意意中使用很多实现的细节,而这些实现的细节在未的Go言中很可能被改
意的是unsafe包是一采用特殊方式實現的包。然它可以和普通包一樣的導入和使用,但它實際上是由編譯器實現的。它提供了一些訪問語言內部特性的方法,特别是存布局相關的細節。將這些特性封到一個獨立的包中,是爲在極少數情況下需要使用的候,同引起人們的註意(譯註:因看包的名字就知道使用unsafe包是不安全的。此外有一些境因安全的因素可能限製這個包的使用。
意的是unsafe包是一采用特殊方式实现的包。然它可以和普通包一样的导入和使用,但它实际上是由编译器实现的。它提供了一些访问语言内部特性的方法,特别是存布局相关的细节。将这些特性封到一个独立的包中,是为在极少数情况下需要使用的候,同引起人们的注意(译注:因看包的名字就知道使用unsafe包是不安全的。此外有一些境因安全的因素可能限制这个包的使用。
unsafe包被泛地用於比較低級的包, 例如runtime、os、syscall有net包等爲它們需要和操作繫統密切配合,但是對於普通的程序一般是不需要使用unsafe包的。
unsafe包被广泛地用于比较低级的包, 例如runtime、os、syscall有net包等为它们需要和操作系统密切配合,但是对于普通的程序一般是不需要使用unsafe包的。