mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-03 14:32:38 +00:00
make loop
This commit is contained in:
@@ -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個字節), 其它的類型對齊到機器字大小.
|
||||
|
||||
|
@@ -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]也是對應相同的地址,這可以用於區分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
|
||||
|
@@ -4,7 +4,7 @@ Go程序可能會遇到要訪問C語言的某些硬件驅動函數的場景,
|
||||
|
||||
在本節中,我們將構建一個簡易的數據壓縮程序,使用了一個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
|
||||
@@ -16,7 +16,7 @@ bzip2壓縮算法,是基於優雅的Burrows-Wheeler變換算法,運行速度
|
||||
|
||||
如果是比較小的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
|
||||
@@ -37,7 +37,7 @@ 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代碼,對應一個獨立的文件。
|
||||
|
||||
@@ -106,7 +106,7 @@ func NewWriter(out io.Writer) io.WriteCloser {
|
||||
|
||||
在cgo註釋中還可以包含#cgo指令,用於給C語言工具鏈指定特殊的參數。例如CFLAGS和LDFLAGS分别對應傳給C語言編譯器的編譯參數和鏈接器參數,使它們可以特定目録找到bzlib.h頭文件和libbz2.a庫文件。這個例子假設你已經在/usr目録成功安裝了bzip2庫。如果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類型併不相同,卽使它們具有相同的大小也是不同的類型。
|
||||
|
||||
@@ -132,9 +132,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.
|
||||
@@ -162,7 +162,7 @@ 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的內容。
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# 第13章 底層編程
|
||||
|
||||
Go語言的設計包含了諸多安全策略,限製了可能導致程序運行齣現錯誤的用法。編譯時類型檢査檢査可以發現大多數類型不匹配的操作,例如兩個字符串做減法的錯誤。字符串、map、slice和chan等所有的內置類型,都有嚴格的類型轉換規則。
|
||||
Go語言的設計包含了諸多安全策略,限製了可能導致程序運行出現錯誤的用法。編譯時類型檢査檢査可以發現大多數類型不匹配的操作,例如兩個字符串做減法的錯誤。字符串、map、slice和chan等所有的內置類型,都有嚴格的類型轉換規則。
|
||||
|
||||
對於無法靜態檢測到的錯誤,例如數組訪問越界或使用空指針,運行時動態檢測可以保證程序在遇到問題的時候立卽終止併打印相關的錯誤信息。自動內存管理(垃圾內存自動迴收)可以消除大部分野指針和內存洩漏相關的問題。
|
||||
|
||||
|
Reference in New Issue
Block a user