Golang 記憶體模型詳解(一)

Golang 記憶體模型詳解(一)

開始之前

首先,這是一篇菜B寫的文章,可能會有理解錯誤的地方,發現錯誤請斧正,謝謝。

為了治療我的懶癌早期,我一次就不寫得太多了,這個系列想寫很久了,每次都是開了個頭就沒有再寫。這次爭取把寫完,弄成一個系列。

此 nil 不等彼 nil

先宣告,這個標題有標題黨的嫌疑。

Go 的型別系統是比較奇葩的,nil 的含義跟其它語言有些差別,這裡舉個例子(可以直接進入 http://play.golang.org/p/ezFhXX0dnB 執行檢視結果):

複製程式碼 程式碼如下:
package main
import “fmt”
type A struct {
}
func main() {
    var a *A = nil
    var ai interface{} = a
    var ei interface{} = nil
    fmt.Printf(“ai == nil: %v\n”, ai == nil)
    fmt.Printf(“ai == ei: %v\n”, ai == ei)
    fmt.Printf(“ei == a: %v\n”, a == ei)
    fmt.Printf(“ei == nil: %v\n”, ei == nil)
}
// -> 輸出
// ai == nil: false
// ai == ei: false
// ei == a: false
// ei == nil: true

這裡 ai != nil,對於沒有用過 Go 的人來說比較費解,對我來說,這個算得上一門語言設計有歧義的地方(Golang FAQ 有對於此問題的描述,可以參考一下:http://golang.org/doc/faq#nil_error)。

簡單的說就是 nil 代表 “zero value”(空值),對於不同型別,它具體所代表的值不同。比如上面的 a 為“*A 型別的空值”,而 ai 為“interface{} 型別的空值”。造成理解失誤的最大問題在於,struct pointer 到 interface 有隱式轉換(var ai interface{] = a,這裡有個隱式轉換),至於為什麼對於 Go 這種在其它轉換方面要求嚴格,而對於 interface 要除外呢,for convenience 吧,呵呵……

碰到了這個坑,我就開始好奇了,Go 的型別系統到底是什麼樣的?

Go 記憶體模型 – interface

概述

為了讀懂下面的內容,你需要:

瞭解 C、Go 語言

Go 1.3 原始碼 (https://go.googlecode.com/archive/go1.3.zip)

PS: 由於 Go 用到了 Plan9 C 這個小眾的C編譯器的擴充套件,比如在函式簽名中使用 · 字元以區分 package/function(比如runtime·panic),這對理解不會產生什麼影響。

PSS: 對於 Go runtime,可以參考src/pkg/reflect(reflect包)中的的程式碼,對型別系統的實現的理解有幫助。

Go 語言的型別定義可以在 src/pkg/runtime/ 目錄下找到,主要由以下幾個檔案構成:

1.runtime.h
2.type.h

對於 interface 型別,主要看下面幾個結構體定義:

1.InterfaceType
2.Itab
3.Iface
4.Eface

它們的C語言定義如下 (可以在 runtime.h 中找到):

InterfaceType:

代表了總的 interface 型別,其中:

1.Type: 型別描述,所有的型別都有這個型別描述(比如 array, map, slice)
2.mhdr 以及 m: interface 介面方法列表

複製程式碼 程式碼如下:
struct InterfaceType
{
    Type;
    Slice mhdr;
    IMethod m[];
};

Itab:

類似於虛擬函式表,該表不會被GC回收,其中:

1.inter: 指向具體的 interface 型別
2.type: 具體實現型別, 也即 receiver type
3.link: 指向下一個函式表,因為 interface 可以 embed 多個 interface,因此實現為一個連結串列形式
4.bad: <略>
5.unsued: <略>
6.fun: 函式列表,每個元素是一個指向具體函式實現的指標

複製程式碼 程式碼如下:
struct  Itab
{
    InterfaceType*  inter;
    Type*   type;
    Itab*   link;
    int32   bad;
    int32   unused;
    void    (*fun[])(void);
};

Iface:

該型別為一般的 interface 型別所對應的資料結構,其中:

1.tab: 參見 Itab 的說明,尤其是 Itab::link
2.data: 指向具體資料(比如指向struct,當然,如果一個資料不超過一個字長,那麼這個data就可以直接存放,不需要指標再做以及跳轉)

複製程式碼 程式碼如下:
struct Iface
{
    Itab*   tab;
    void*   data;
};

Eface:

該型別為 interface{} (empty interface) 所對應的資料結構,其中:

1.type: 具體實現型別, 也即 receiver type
2.data: 同 Iface

複製程式碼 程式碼如下:
struct Eface
{
    Type*   type;
    void*   data;
};

他們的依賴關係如下圖所示:

先到這裡,下一篇將會舉例子說明給一個 interface{} 型別的變數賦值後,其具體的記憶體結構是怎麼樣的。

打了幾個小時,真費時間,爭取這個系列不坑 (逃

您可能感興趣的文章:

Go語言共享記憶體讀寫例項分析Go語言中的Array、Slice、Map和Set使用詳解Go語言的GOPATH與工作目錄詳解Go語言命令列操作命令詳細介紹Go語言interface詳解Go語言執行環境安裝詳細教程Go語言實現簡單的一個靜態WEB伺服器GO語言併發程式設計之互斥鎖、讀寫鎖詳解GO語言標準錯誤處理機制error用法例項Go語言中的記憶體佈局詳解