update tw

This commit is contained in:
chai2010
2015-12-18 10:53:03 +08:00
parent 510c741a6f
commit c66a96ee52
106 changed files with 864 additions and 864 deletions

View File

@@ -1,19 +1,19 @@
## 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 等較大的數據類型.
由於這個因素,一個聚類型(結構體或數組)的大小至少是所有字段或元素大小的總和, 或者更大因可能存在空洞. 空洞是編譯器自動添加的沒有被使用的空間, 用於保證後麫每個字段或元素的地址相對於結構或數組的開始地址能夠理地對齊.
由於這個因素,一個聚類型(結構體或數組)的大小至少是所有字段或元素大小的總和, 或者更大因可能存在空洞. 空洞是編譯器自動添加的沒有被使用的空間, 用於保証後面每個字段或元素的地址相對於結構或數組的開始地址能夠理地對齊.
類型 | 大小
@@ -29,7 +29,7 @@ func | 1個機器字
chan | 1個機器字
interface | 2個機器字(type,value)
Go的語言規範沒有保一個字段的聲明順序和內存中的順序是一緻的, 所以理論上一個編譯器可以隨意地重新排列每個字段的內存佈侷, 隨着在寫作本書的時候編譯器還沒有這做. 下的三個結構體有着相的字段, 但是第一個比另外的兩個需要多 50% 的內存.
Go的語言規範沒有保一個字段的聲明順序和內存中的順序是一緻的, 所以理論上一個編譯器可以隨意地重新排列每個字段的內存佈侷, 隨着在寫作本書的時候編譯器還沒有這做. 下的三個結構體有着相的字段, 但是第一個比另外的兩個需要多 50% 的內存.
```Go
@@ -41,7 +41,7 @@ struct{ bool; int16; float64 } // 2 words 3words
雖然關於對齊算法的細節超齣了本書的範圍, 也不是每一個結構體都需要擔心這個問題, 不過有效的包裝可以使數據結構更加緊湊, 內存使用率和性能都可能受益.
`unsafe.Alignof` 函數返迴對應參數的類型需要對齊的倍數. 和 Sizeof 類似, Alignof 也是返迴一個常量達式, 對應一個常量. 通常情況下佈爾和數字類型需要對齊到它們本身的大小(最多8個字節), 其它的類型對齊到機器字大小.
`unsafe.Alignof` 函數返迴對應參數的類型需要對齊的倍數. 和 Sizeof 類似, Alignof 也是返迴一個常量達式, 對應一個常量. 通常情況下佈爾和數字類型需要對齊到它們本身的大小(最多8個字節), 其它的類型對齊到機器字大小.
`unsafe.Offsetof` 函數的參數必鬚是一個字段 `x.f`, 然後返迴 `f` 字段相對於 `x` 起始地址的偏移量, 包括可能的空洞.
@@ -57,7 +57,7 @@ var x struct {
The table below shows the results of applying the three unsafe functions to x itself and to each of its three fields:
顯示了應用三個函數對 x 和它的三個字段計算的結果:
顯示了應用三個函數對 x 和它的三個字段計算的結果:
![](../images/ch13-01.png)
@@ -80,6 +80,6 @@ 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` 是特定義的一種指鍼類型, 它可以包含任意類型變量的地址. 當然, 我們不可以直接使用 *p `unsafe.Pointer` 指鍼指的眞實變量, 因我們不知道變量的類型. 和普通指鍼一樣, `unsafe.Pointer` 指鍼是可以比較的, 支持和 nil 比較判斷是否空指鍼.
大多數指鍼類型寫成 *T, 含義是 "一個指T類型變量的指鍼". `unsafe.Pointer` 是特定義的一種指鍼類型, 它可以包含任意類型變量的地址. 當然, 我們不可以直接使用 *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,13 +12,13 @@ func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
```
通過新指鍼, 我們可以更新浮點數的位模式. 通過位模式操作浮點數是可以的, 但是更重要的意義是指鍼轉換讓我們可以在不破壞類型繫統的前提下內存寫入任意的值.
通過新指鍼, 我們可以更新浮點數的位模式. 通過位模式操作浮點數是可以的, 但是更重要的意義是指鍼轉換讓我們可以在不破壞類型繫統的前提下內存寫入任意的值.
一個 `unsafe.Pointer` 指鍼也可以被轉化 uintptr 類似, 然後保存到指鍼型數值變量中, 用以做必要的指鍼運算.
一個 `unsafe.Pointer` 指鍼也可以被轉化 uintptr 類似, 然後保存到指鍼型數值變量中, 用以做必要的指鍼運算.
(第三章內容, uintptr是一個無符號的整型數, 足有保存一個地址.)
這種轉換也是可逆的, 但是, 將 uintptr 轉 `unsafe.Pointer` 指鍼可能破壞類型繫統, 因為併不是所有的數字都是有效的內存地址.
這種轉換也是可逆的, 但是, 將 uintptr 轉 `unsafe.Pointer` 指鍼可能破壞類型繫統, 因爲並不是所有的數字都是有效的內存地址.
許多將 `unsafe.Pointer` 指鍼 轉原生數字, 然後再轉 `unsafe.Pointer` 指鍼的操作是不安全的. 下的例子需要將變量 x 的地址加上 b 字段的偏移轉化 *int16 類型指鍼, 然後通過該指鍼更新 `x.b`:
許多將 `unsafe.Pointer` 指鍼 轉原生數字, 然後再轉 `unsafe.Pointer` 指鍼的操作是不安全的. 下的例子需要將變量 x 的地址加上 b 字段的偏移轉化 *int16 類型指鍼, 然後通過該指鍼更新 `x.b`:
```Go
//gopl.io/ch13/unsafeptr
@@ -36,9 +36,9 @@ pb := (*int16)(unsafe.Pointer(
fmt.Println(x.b) // "42"
```
管寫法很繁瑣, 但在這裏併不是一件壞事, 因這些功能應該很謹慎地使用. 不要試圖將引入可能而破壞代碼的正確性的 uintptr 臨時變量. 下段代碼是不正確的:
管寫法很繁瑣, 但在這裡並不是一件壞事, 因這些功能應該很謹慎地使用. 不要試圖將引入可能而破壞代碼的正確性的 uintptr 臨時變量. 下段代碼是不正確的:
錯誤的原因很微妙. 有時候垃圾迴收器會移動一些變量以降低內存碎片的問題.這類垃圾迴收器被稱移動GC. 當一個變量被移動, 所有的保存改變量舊地址的指鍼必鬚時被更新變量移動後的新地址. 從垃圾收集器的視角來看, 一個 `unsafe.Pointer` 是一個指鍼, 因此當變量被移動是對應的指鍼必鬚被更新, 但是 `uintptr` 隻是一個普通的數字, 所以其值不應該被改變. 上錯誤的代碼因一個非指鍼的臨時變量 `tmp`, 導緻垃圾收集器無法正確識這個是一個指變量 `x` 的指鍼. 第二個語句執行時, 變量 `x` 可能已經被轉移, 臨時變量 `tmp` 也就不在對應現在的 `&x.b`. 第三個賦值語句將徹底摧那個之前的那部分內存空間.
錯誤的原因很微妙. 有時候垃圾迴收器會移動一些變量以降低內存碎片的問題.這類垃圾迴收器被稱移動GC. 當一個變量被移動, 所有的保存改變量舊地址的指鍼必鬚時被更新變量移動後的新地址. 從垃圾收集器的視角來看, 一個 `unsafe.Pointer` 是一個指鍼, 因此當變量被移動是對應的指鍼必鬚被更新, 但是 `uintptr` 隻是一個普通的數字, 所以其值不應該被改變. 上錯誤的代碼因一個非指鍼的臨時變量 `tmp`, 導緻垃圾收集器無法正確識這個是一個指變量 `x` 的指鍼. 第二個語句執行時, 變量 `x` 可能已經被轉移, 臨時變量 `tmp` 也就不在對應現在的 `&x.b`. 第三個賦值語句將徹底摧那個之前的那部分內存空間.
有很多類似原因導緻的錯誤. 例如這條語句:
@@ -46,14 +46,14 @@ fmt.Println(x.b) // "42"
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯誤!
```
裏併沒有指鍼引用 `new` 新創建的變量, 因此語句執行完成之後, 垃圾收集器有權迴收其內存空間, 所以返迴的 `pT` 保存將是無效的地址.
裡並沒有指鍼引用 `new` 新創建的變量, 因此語句執行完成之後, 垃圾收集器有權迴收其內存空間, 所以返迴的 `pT` 保存將是無效的地址.
目前的Go語言實現還沒有使用移動GC(未來可能實現), 但這不該是僥倖的理由: 當前的Go實現已經有移動變量的場景. 在5.2節我們提到goroutine的棧是根據需要動態增長的. 當這個時候, 原來棧中的所以變量可能需要被移動到新的更大的棧中, 所以我們無法確保變量的地址在整個使用週期內保持不變.
在編寫本文時, 還沒有清晰的原則就指引Go程序員, 什`unsafe.Pointer``uintptr` 的轉換是不安全的(參考 [Go issue7192](https://github.com/golang/go/issues/7192). 譯註: 該問題已經脩復.), 因此我們強烈建議按照最壞的方式處理. 將所有包含變量 `y` 地址的 `uintptr` 類型變量當作 BUG 處理, 時減少不必要的 `unsafe.Pointer``uintptr` 的轉換. 在第一個例子中, 有三個到 `uintptr` 的轉換, 字段偏移量的運算, 所有的轉換全在一個達式完成.
在編寫本文時, 還沒有清晰的原則就指引Go程序員, 什`unsafe.Pointer``uintptr` 的轉換是不安全的(參考 [Go issue7192](https://github.com/golang/go/issues/7192). 譯註: 該問題已經脩復.), 因此我們強烈建議按照最壞的方式處理. 將所有包含變量 `y` 地址的 `uintptr` 類型變量當作 BUG 處理, 時減少不必要的 `unsafe.Pointer``uintptr` 的轉換. 在第一個例子中, 有三個到 `uintptr` 的轉換, 字段偏移量的運算, 所有的轉換全在一個達式完成.
當調用一個庫函數, 且返迴的是 `uintptr` 類型是, 比如下反射包中的相關函數,
返迴的結果應該立卽轉換 `unsafe.Pointer` 以確保指鍼指的是相的變量.
當調用一個庫函數, 且返迴的是 `uintptr` 類型是, 比如下反射包中的相關函數,
返迴的結果應該立卽轉換 `unsafe.Pointer` 以確保指鍼指的是相的變量.
```Go
package reflect

View File

@@ -1,6 +1,6 @@
## 13.3. 示例: 深度相等判斷
來自 reflect 包的 DeepEqual 對兩個值進行深度相等判斷. DeepEqual 使用內建的 `==` 操作符對基礎類型進行相等判斷, 對於復類型則遞歸變量每個基礎類型然後做類似的比較判斷. 因它工作在任意的類型上, 甚至對一些不支持 `==` 操作符的類型也可以工作, 因此在一些測試代碼中被廣地使用. 比如下的代碼是用 DeepEqual 比較兩個字符串數組是否等價.
來自 reflect 包的 DeepEqual 對兩個值進行深度相等判斷. DeepEqual 使用內建的 `==` 操作符對基礎類型進行相等判斷, 對於復類型則遞歸變量每個基礎類型然後做類似的比較判斷. 因它工作在任意的類型上, 甚至對一些不支持 `==` 操作符的類型也可以工作, 因此在一些測試代碼中被廣地使用. 比如下的代碼是用 DeepEqual 比較兩個字符串數組是否等價.
```Go
func TestSplit(t *testing.T) {
@@ -10,9 +10,9 @@ func TestSplit(t *testing.T) {
}
```
管 DeepEqual 很方便, 而且可以支持任意的類型, 但是也有不足之處.
管 DeepEqual 很方便, 而且可以支持任意的類型, 但是也有不足之處.
例如, 它將一個 nil map 和 非 nil 的空的 map 視作不相等,
樣 nil slice 和 非 nil 的空的 slice 也不相等.
樣 nil slice 和 非 nil 的空的 slice 也不相等.
```Go
var a, b []string = nil, []string{}
@@ -22,7 +22,7 @@ var c, d map[string]int = nil, make(map[string]int)
fmt.Println(reflect.DeepEqual(c, d)) // "false"
```
在這定義一個自己的 Equal 函數用於比較人員的值. 和 DeepEqual 類似的是它也是基於 slice 和 map 的元素進行遞歸比較, 不之處是它將 nil slice(map類似) 和非 nil 的空 slice 視作相等的值. 基礎部分的比較可以基於反射完成, 和 12.3 章的 Display 實現方法類似. 樣, 我們頂一個一個內部函數 equal, 用於內部的遞歸比較. 目前不用關心 seen 參數. 對於每一對需要比較的 x 和 y, equal 函數 首先檢測它們是否都有效(或都無效), 然後檢測它們是否是相的類型. 剩下的部分是一個大的 switch 分支, 用於擁有相基礎類型的比較. 因為頁麫空間的限製, 我們省略了一些類似的分支.
在這定義一個自己的 Equal 函數用於比較人員的值. 和 DeepEqual 類似的是它也是基於 slice 和 map 的元素進行遞歸比較, 不之處是它將 nil slice(map類似) 和非 nil 的空 slice 視作相等的值. 基礎部分的比較可以基於反射完成, 和 12.3 章的 Display 實現方法類似. 樣, 我們頂一個一個內部函數 equal, 用於內部的遞歸比較. 目前不用關心 seen 參數. 對於每一對需要比較的 x 和 y, equal 函數 首先檢測它們是否都有效(或都無效), 然後檢測它們是否是相的類型. 剩下的部分是一個大的 switch 分支, 用於擁有相基礎類型的比較. 因爲頁面空間的限製, 我們省略了一些類似的分支.
```Go
gopl.io/ch13/equal
@@ -65,8 +65,8 @@ func equal(x, y reflect.Value, seen map[comparison]bool) bool {
}
```
和前的建議一樣, 我們不公開使用反射相關的接口,
所以導齣的函數需要在內部自己將變量轉 reflect.Value 類型.
和前的建議一樣, 我們不公開使用反射相關的接口,
所以導齣的函數需要在內部自己將變量轉 reflect.Value 類型.
```Go
// Equal reports whether x and y are deeply equal.
@@ -81,7 +81,7 @@ type comparison struct {
}
```
了確保算法對於循環數據結構也能正常退齣, 我們必鬚記每次已經比較的變量, 從而避免進入第二次的比較. Equal 函數分配了一組用於比較的結構體, 包含每對比較對象的地址(unsafe.Pointer形式保存)和類型. 我們記類型的原因是, 有些不的變量可能對應相的地址. 例如, 如果 x 和 y 都是數組類型, 那 x 和 `x[0]` 將對應相的地址, y 和 `y[0]` 也是對應相的地址, 這可以用於判斷 對x 和 y 比較 或 x[0] 和 y[0] 的是否進行過了.
了確保算法對於循環數據結構也能正常退齣, 我們必鬚記每次已經比較的變量, 從而避免進入第二次的比較. Equal 函數分配了一組用於比較的結構體, 包含每對比較對象的地址(unsafe.Pointer形式保存)和類型. 我們記類型的原因是, 有些不的變量可能對應相的地址. 例如, 如果 x 和 y 都是數組類型, 那 x 和 `x[0]` 將對應相的地址, y 和 `y[0]` 也是對應相的地址, 這可以用於判斷 對x 和 y 比較 或 x[0] 和 y[0] 的是否進行過了.
```Go
// cycle check

View File

@@ -1,10 +1,10 @@
## 13.4. 通過cgo調用C代碼
Go程序可能會遇到要訪問C語言的某些硬件驅動的場景, 或者是從一個C++實現的嵌入式數據庫査詢記的場景, 或者是使用Fortran實現的一些綫性代數庫的場景. C作一個通用語言, 很多庫會選擇提供一個C兼容的API, 然後用其他語言實現.
Go程序可能會遇到要訪問C語言的某些硬件驅動的場景, 或者是從一個C++實現的嵌入式數據庫査詢記的場景, 或者是使用Fortran實現的一些綫性代數庫的場景. C作一個通用語言, 很多庫會選擇提供一個C兼容的API, 然後用其他語言實現.
在本節中, 我們將構建一個簡易的數據壓縮程序, 通過使用一個Go語言自帶的叫cgo的用於支援C語言函數調用的工具. 這類工具被稱外圍函數接口(ffi), 且cgo也不是Go中唯一的類似工具. SWIG(swig.org) 是類似的另一個被廣使用的工具, 它提供了很多復雜特性以支援C++的集成, 但 SWIG 不是這要討論的主題.
在本節中, 我們將構建一個簡易的數據壓縮程序, 通過使用一個Go語言自帶的叫cgo的用於支援C語言函數調用的工具. 這類工具被稱外圍函數接口(ffi), 且cgo也不是Go中唯一的類似工具. SWIG(swig.org) 是類似的另一個被廣使用的工具, 它提供了很多復雜特性以支援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
@@ -14,13 +14,13 @@ func NewReader(r io.Reader) (io.ReadCloser, error)
bzip2壓縮算法, 是基於優雅的 Burrows-Wheeler 變換, 運行速度比 gzip 要慢, 但是可以提供更高的壓縮比. 標準庫的 `compress/bzip2` 包目前還沒有提供 bzip2 算法的壓縮實現. 完全從頭實現是一個繁瑣的工作, 而且 bzip.org 有現成的 libbzip2 開源實現, 文檔齊全而且性能較好,
如果C庫比較小, 我們可以用純Go重新實現一遍. 如果我們對性能沒有特殊要求, 我們可以用 `os/exec` 包的方法將C編寫的應用程序作一個子進行運行. 隻有當你需要使用復雜但是性能更高的底層C接口時, 就是使用cgo的場景了. 下我們將通過一個例子講述cgo的用法.
如果C庫比較小, 我們可以用純Go重新實現一遍. 如果我們對性能沒有特殊要求, 我們可以用 `os/exec` 包的方法將C編寫的應用程序作一個子進行運行. 隻有當你需要使用復雜但是性能更高的底層C接口時, 就是使用cgo的場景了. 下我們將通過一個例子講述cgo的用法.
要使用 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代碼, 對應一個獨立的文件.
```C
gopl.io/ch13/bzip
@@ -42,7 +42,7 @@ int bz2compress(bz_stream *s, int action,
}
```
現在讓我們轉到Go部分, 第一部分如下所示. 其中 `import "C"` 的語句是比較特的. 其實沒有一個叫 `C` 的包, 但是這行語句會讓Go構建在編譯之前先運行cgo工具.
現在讓我們轉到Go部分, 第一部分如下所示. 其中 `import "C"` 的語句是比較特的. 其實沒有一個叫 `C` 的包, 但是這行語句會讓Go構建在編譯之前先運行cgo工具.
```Go
@@ -82,7 +82,7 @@ func NewWriter(out io.Writer) io.WriteCloser {
}
```
在循環的每次迭代中, bz2compress傳入數據的地址和剩餘部分的長度, 還有輸齣緩存 w.outbuf 的地址和容量. 這兩個長度信息通過它們的地址傳入而不是值傳入, 因bz2compress函數可能會根據已經壓縮的數據和壓縮後數據的大小來更新這兩個值(譯註: 這的用法有問題, 勘誤已經提到. 具體脩復的方法稍後再補充). 每個塊壓縮後的數據被寫入到底層的 io.Writer.
在循環的每次迭代中, bz2compress傳入數據的地址和剩餘部分的長度, 還有輸齣緩存 w.outbuf 的地址和容量. 這兩個長度信息通過它們的地址傳入而不是值傳入, 因bz2compress函數可能會根據已經壓縮的數據和壓縮後數據的大小來更新這兩個值(譯註: 這的用法有問題, 勘誤已經提到. 具體脩復的方法稍後再補充). 每個塊壓縮後的數據被寫入到底層的 io.Writer.
Close 方法和 Write 方法有着類似的結構, 通過一個循環將剩餘的壓縮數據刷新到輸齣緩存.
@@ -111,11 +111,11 @@ func (w *writer) Close() error {
}
```
壓縮完成後, Close 用了 defer 確保函數退齣前調用 C.BZ2_bzCompressEnd 釋放輸入和輸齣流的緩存. 此刻 `w.stream` 指鍼將不在有效, 我們將它設置 nil 以保安全, 然後在每個方法中增加 nil 檢測, 以防止用戶在關閉後依然錯誤使用相關方法.
壓縮完成後, Close 用了 defer 確保函數退齣前調用 C.BZ2_bzCompressEnd 釋放輸入和輸齣流的緩存. 此刻 `w.stream` 指鍼將不在有效, 我們將它設置 nil 以保安全, 然後在每個方法中增加 nil 檢測, 以防止用戶在關閉後依然錯誤使用相關方法.
不僅僅寫是非併髮安全的, 甚至併髮調用 Close 和 Write 也可能導緻C代碼的崩潰. 脩復這個問題是 練習13.3 的內容.
不僅僅寫是非並發安全的, 甚至並發調用 Close 和 Write 也可能導緻C代碼的崩潰. 脩復這個問題是 練習13.3 的內容.
的bzipper程序是使用我們自己包實現的bzip2壓縮命令. 它的行和許多Unix繫統的 bzip2 命令類似.
的bzipper程序是使用我們自己包實現的bzip2壓縮命令. 它的行和許多Unix繫統的 bzip2 命令類似.
```Go
gopl.io/ch13/bzipper
@@ -141,7 +141,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
@@ -155,8 +155,8 @@ $ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum
126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed -
```
我們演示了將一個C庫鏈接到Go程序. 相反, 將Go編譯靜態庫然後鏈接到C程序, 或者將Go編譯動態庫然後在C程序中動態加載也都是可行的. 這我們隻展示的cgo很小的一些方, 更多的關於內存管理, 指鍼, 迴調函數, 信號處理, 字符串, errno處理, 終結器, 以及 goroutines 和繫統綫程的關繫等, 有很多細節可以討論. 特是如何將Go的指鍼傳入C函數的規則也是異常復雜的, 部分的原因在 13.2節 有討論到, 但是在Go1.5中還沒有被明確. 如果要進一步閱讀, 可以從 https://golang.org/cmd/cgo 開始.
我們演示了將一個C庫鏈接到Go程序. 相反, 將Go編譯靜態庫然後鏈接到C程序, 或者將Go編譯動態庫然後在C程序中動態加載也都是可行的. 這我們隻展示的cgo很小的一些方, 更多的關於內存管理, 指鍼, 迴調函數, 信號處理, 字符串, errno處理, 終結器, 以及 goroutines 和繫統綫程的關繫等, 有很多細節可以討論. 特是如何將Go的指鍼傳入C函數的規則也是異常復雜的, 部分的原因在 13.2節 有討論到, 但是在Go1.5中還沒有被明確. 如果要進一步閱讀, 可以從 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 的替代實現.
**練習13.4:**C庫依賴的限製. 使用 `os/exec``/bin/bzip2` 命令作一個子進程, 提供一個純Go的 bzip.NewWriter 的替代實現.

View File

@@ -1,10 +1,10 @@
## 13.5. 幾點忠告
我們在前一章結尾的時候, 我們警告要謹慎使用反射. 那些警告樣適用於本章的 unsafe 包.
我們在前一章結尾的時候, 我們警告要謹慎使用反射. 那些警告樣適用於本章的 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

@@ -1,21 +1,21 @@
# 第13章 底層編程
Go的設計包含了諸多安全策略, 限製了可能導緻程序錯誤的用法. 編譯時類型檢査檢測可以現大多數類型不匹配的變量操作, 例如兩個字符串做減法的錯誤. 字符串, 字典, 切片 和管道等所有的內置類型, 都有嚴格的類型轉換規則.
Go的設計包含了諸多安全策略, 限製了可能導緻程序錯誤的用法. 編譯時類型檢査檢測可以現大多數類型不匹配的變量操作, 例如兩個字符串做減法的錯誤. 字符串, 字典, 切片 和管道等所有的內置類型, 都有嚴格的類型轉換規則.
對於無法靜態檢測到的錯誤, 例如數組訪問越界或使用空指鍼, 動態檢測可以保程序在遇到問題的時候立卽終止打印相關的錯誤信息. 自動內存管理(垃圾迴收)消除了大部分野指鍼和內存洩漏的問題.
對於無法靜態檢測到的錯誤, 例如數組訪問越界或使用空指鍼, 動態檢測可以保程序在遇到問題的時候立卽終止打印相關的錯誤信息. 自動內存管理(垃圾迴收)消除了大部分野指鍼和內存洩漏的問題.
Go的實現刻意隱藏了很多底層細節. 我們無法知道一個結構體的內存佈侷, 也無法取一個運行函數的機器碼, 也無法知道當前的 goroutine 是運行在哪個操作繫統綫程上. 事實上, Go的調度器會自己決定是否需要將 goroutine 從一個操作繫統綫程轉移到另一個操作繫統綫程. 一個指變量的指鍼也沒有展示變量眞實的地址. 因垃圾迴收器會根據需要移動變量的位置, 當然對應的也會被自動更新.
Go的實現刻意隱藏了很多底層細節. 我們無法知道一個結構體的內存佈侷, 也無法取一個運行函數的機器碼, 也無法知道當前的 goroutine 是運行在哪個操作繫統綫程上. 事實上, Go的調度器會自己決定是否需要將 goroutine 從一個操作繫統綫程轉移到另一個操作繫統綫程. 一個指變量的指鍼也沒有展示變量眞實的地址. 因垃圾迴收器會根據需要移動變量的位置, 當然對應的也會被自動更新.
總的來說, Go語言的這些特殊使得Go程序相比較低級的C語言來說, 更容易預測, 更容易理解, 也不容易崩潰. 通過隱藏底層的細節, 也使得Go程序具有高度的可移植性, 因語言的語義在很大程度上是獨立於任何編譯器, 操作繫統和CPU繫統結構的(當然也不完全對獨立: 例如CPU字的大小, 某些達式求值的順序, 還有編譯器實現的一些限製).
總的來說, Go語言的這些特殊使得Go程序相比較低級的C語言來說, 更容易預測, 更容易理解, 也不容易崩潰. 通過隱藏底層的細節, 也使得Go程序具有高度的可移植性, 因語言的語義在很大程度上是獨立於任何編譯器, 操作繫統和CPU繫統結構的(當然也不完全對獨立: 例如CPU字的大小, 某些達式求值的順序, 還有編譯器實現的一些限製).
有時候我們可能會放棄部分語言特性而優先選擇更好的性能優化, 與其他語言編寫的庫互操作, 或者不用純Go語言來實現某些函數.
在本章, 我們將展示如何使用 unsafe 包來脫通常的規則限製, 如何創建C函數庫的綁定, 以及如何進行繫統調用.
在本章, 我們將展示如何使用 unsafe 包來脫通常的規則限製, 如何創建C函數庫的綁定, 以及如何進行繫統調用.
本章描述的方法不應該輕易使用. 如果沒有處理好細節, 它們可能導緻各種不可預測的隱晦的錯誤, 甚至連本地的C程序員也無法理解. 使用 unsafe 包時也無法保與未來版本的兼容性, 因在有意無意中會使用很多實現的細節, 而這些實現的細節在未來很可能會改變.
本章描述的方法不應該輕易使用. 如果沒有處理好細節, 它們可能導緻各種不可預測的隱晦的錯誤, 甚至連本地的C程序員也無法理解. 使用 unsafe 包時也無法保與未來版本的兼容性, 因在有意無意中會使用很多實現的細節, 而這些實現的細節在未來很可能會改變.
unsafe 包的實現比較特殊. 雖然它可以和普通包一樣的導入和使用, 但它實際上是由編譯器實現的. 它提供了一些訪問語言內部特性的方法, 特是內存佈侷相關的細節.
將這些特封裝到一個獨立的包中, 是在極少數情況下需要使用的時候, 引起人們的註意(它們是不安全的). 此外, 有一些環境因安全的因素可能限製這個包的使用.
unsafe 包的實現比較特殊. 雖然它可以和普通包一樣的導入和使用, 但它實際上是由編譯器實現的. 它提供了一些訪問語言內部特性的方法, 特是內存佈侷相關的細節.
將這些特封裝到一個獨立的包中, 是在極少數情況下需要使用的時候, 引起人們的註意(它們是不安全的). 此外, 有一些環境因安全的因素可能限製這個包的使用.
unsafe 包被廣地用於比較低級的包, 例如 runtime, os, syscall 還有 net 等, 因它們需要和操作繫統密切配的, 但是普通的程序一般是不需要的.
unsafe 包被廣地用於比較低級的包, 例如 runtime, os, syscall 還有 net 等, 因它們需要和操作繫統密切配的, 但是普通的程序一般是不需要的.