日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

Go 接口:nil接口为什么不等于nil?

發(fā)布時間:2023/11/16 windows 82 coder
生活随笔 收集整理的這篇文章主要介紹了 Go 接口:nil接口为什么不等于nil? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Go 接口:nil接口為什么不等于nil?

本文主要內(nèi)容:深入了解接口類型的運(yùn)行時表示層。

目錄
  • Go 接口:nil接口為什么不等于nil?
    • 一、Go 接口的地位
    • 二、接口的靜態(tài)特性與動態(tài)特性
      • 2.1 接口的靜態(tài)特性與動態(tài)特性介紹
      • 2.2 “動靜皆備”的特性的好處
    • 三、nil error 值 != nil
    • 四、接口類型變量的內(nèi)部表示
      • 第一種:nil 接口變量
      • 第二種:空接口類型變量
      • 第三種:非空接口類型變量
      • 第四種:空接口類型變量與非空接口類型變量的等值比較
    • 五、輸出接口類型變量內(nèi)部表示的詳細(xì)信息
    • 六、接口類型的裝箱(boxing)原理
    • 七、小結(jié)

一、Go 接口的地位

Go 語言核心團(tuán)隊(duì)的技術(shù)負(fù)責(zé)人 Russ Cox 也曾說過這樣一句話:“如果要從 Go 語言中挑選出一個特性放入其他語言,我會選擇接口”,這句話足以說明接口這一語法特性在這位 Go 語言大神心目中的地位。

為什么接口在 Go 中有這么高的地位呢?這是因?yàn)?strong>接口是 Go 這門靜態(tài)語言中唯一“動靜兼?zhèn)洹钡恼Z法特性。而且,接口“動靜兼?zhèn)洹钡奶匦越o Go 帶來了強(qiáng)大的表達(dá)能力,但同時也給 Go 語言初學(xué)者帶來了不少困惑。要想真正解決這些困惑,我們必須深入到 Go 運(yùn)行時層面,看看 Go 語言在運(yùn)行時是如何表示接口類型的。

接下來,我們先來看看接口的靜態(tài)與動態(tài)特性,看看“動靜皆備”的含義。

二、接口的靜態(tài)特性與動態(tài)特性

2.1 接口的靜態(tài)特性與動態(tài)特性介紹

接口的靜態(tài)特性體現(xiàn)在接口類型變量具有靜態(tài)類型。

比如 var err error 中變量 err 的靜態(tài)類型為 error。擁有靜態(tài)類型,那就意味著編譯器會在編譯階段對所有接口類型變量的賦值操作進(jìn)行類型檢查,編譯器會檢查右值的類型是否實(shí)現(xiàn)了該接口方法集合中的所有方法。如果不滿足,就會報錯:

var err error = 1 // cannot use 1 (type int) as type error in assignment: int does not implement error (missing Error method)

**而接口的動態(tài)特性,就體現(xiàn)在接口類型變量在運(yùn)行時還存儲了右值的真實(shí)類型信息,這個右值的真實(shí)類型被稱為接口類型變量的動態(tài)類型。例如,下面示例代碼:

var err error
err = errors.New("error1")
fmt.Printf("%T\n", err)  // *errors.errorString

我們可以看到,這個示例通過 errros.New 構(gòu)造了一個錯誤值,賦值給了 error 接口類型變量 err,并通過 fmt.Printf 函數(shù)輸出接口類型變量 err 的動態(tài)類型為 *errors.errorString

2.2 “動靜皆備”的特性的好處

首先,接口類型變量在程序運(yùn)行時可以被賦值為不同的動態(tài)類型變量,每次賦值后,接口類型變量中存儲的動態(tài)類型信息都會發(fā)生變化,這讓 Go 語言可以像動態(tài)語言(比如 Python)那樣擁有使用 Duck Typing(鴨子類型)的靈活性。所謂鴨子類型,就是指某類型所表現(xiàn)出的特性(比如是否可以作為某接口類型的右值),不是由其基因(比如 C++ 中的父類)決定的,而是由類型所表現(xiàn)出來的行為(比如類型擁有的方法)決定的。

比如下面的例子:

type QuackableAnimal interface {
    Quack()
}

type Duck struct{}

func (Duck) Quack() {
    println("duck quack!")
}

type Dog struct{}

func (Dog) Quack() {
    println("dog quack!")
}

type Bird struct{}

func (Bird) Quack() {
    println("bird quack!")
}                         
                          
func AnimalQuackInForest(a QuackableAnimal) {
    a.Quack()             
}                         
                          
func main() {             
    animals := []QuackableAnimal{new(Duck), new(Dog), new(Bird)}
    for _, animal := range animals {
        AnimalQuackInForest(animal)
    }  
}

這個例子中,我們用接口類型 QuackableAnimal 來代表具有“會叫”這一特征的動物,而 DuckBirdDog 類型各自都具有這樣的特征,于是我們可以將這三個類型的變量賦值給 QuackableAnimal 接口類型變量 a。每次賦值,變量 a 中存儲的動態(tài)類型信息都不同,Quack 方法的執(zhí)行結(jié)果將根據(jù)變量 a 中存儲的動態(tài)類型信息而定。

這里的 DuckBirdDog 都是“鴨子類型”,但它們之間并沒有什么聯(lián)系,之所以能作為右值賦值給 QuackableAnimal 類型變量,只是因?yàn)樗麄儽憩F(xiàn)出了 QuackableAnimal 所要求的特征罷了。

不過,與動態(tài)語言不同的是,Go 接口還可以保證“動態(tài)特性”使用時的安全性。比如,編譯器在編譯期就可以捕捉到將 int 類型變量傳給 QuackableAnimal 接口類型變量這樣的明顯錯誤,決不會讓這樣的錯誤遺漏到運(yùn)行時才被發(fā)現(xiàn)。

接口類型的動靜特性展示了其強(qiáng)大的一面,然而在日常使用中,對Gopher常常困惑與“nil 的 error 值不等于 nil”。下面我們來詳細(xì)看一下。

三、nil error 值 != nil

我們先來看一段改編自GO FAQ 中的例子的代碼:

type MyError struct {
    error
}

var ErrBad = MyError{
    error: errors.New("bad things happened"),
}

func bad() bool {
    return false
}

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = &ErrBad
    }
    return p
}

func main() {
    err := returnsError()
    if err != nil {
        fmt.Printf("error occur: %+v\n", err)
        return
    }
    fmt.Println("ok")
}

在這個例子中,我們的關(guān)注點(diǎn)集中在 returnsError 這個函數(shù)上面。這個函數(shù)定義了一個 *MyError 類型的變量 p,初值為 nil。如果函數(shù) bad 返回 falsereturnsError 函數(shù)就會直接將 p(此時 p = nil)作為返回值返回給調(diào)用者,之后調(diào)用者會將 returnsError 函數(shù)的返回值(error 接口類型)與 nil 進(jìn)行比較,并根據(jù)比較結(jié)果做出最終處理。

我們運(yùn)行這段程序后,輸出如下:

error occur: <nil>

按照預(yù)期:程序執(zhí)行應(yīng)該是pnilreturnsError 返回 p,那么 main 函數(shù)中的 err 就等于 nil,于是程序輸出 ok 后退出。但是我們看到,示例程序并未按照預(yù)期,程序顯然是進(jìn)入了錯誤處理分支,輸出了 err 的值。那這里就有一個問題了:明明 returnsError 函數(shù)返回的 p 值為 nil,為什么卻滿足了 if err != nil 的條件進(jìn)入錯誤處理分支呢?

為了弄清楚這個問題,我們來了解接口類型變量的內(nèi)部表示。

四、接口類型變量的內(nèi)部表示

接口類型“動靜兼?zhèn)洹钡奶匦砸矝Q定了它的變量的內(nèi)部表示絕不像一個靜態(tài)類型變量(如 intfloat64)那樣簡單,我們可以在 $GOROOT/src/runtime/runtime2.go 中找到接口類型變量在運(yùn)行時的表示:

// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

我們看到,在運(yùn)行時層面,接口類型變量有兩種內(nèi)部表示:ifaceeface,這兩種表示分別用于不同的接口類型變量:

  • eface 用于表示沒有方法的空接口(empty interface)類型變量,也就是 interface{} 類型的變量;
  • iface 用于表示其余擁有方法的接口 interface 類型變量。

這兩個結(jié)構(gòu)的共同點(diǎn)是它們都有兩個指針字段,并且第二個指針字段的功能相同,都是指向當(dāng)前賦值給該接口類型變量的動態(tài)類型變量的值。

那它們的不同點(diǎn)在哪呢?就在于 eface 表示的空接口類型并沒有方法列表,因此它的第一個指針字段指向一個 _type 類型結(jié)構(gòu),這個結(jié)構(gòu)為該接口類型變量的動態(tài)類型的信息,它的定義是這樣的:

// $GOROOT/src/runtime/type.go

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

iface 除了要存儲動態(tài)類型信息之外,還要存儲接口本身的信息(接口的類型信息、方法列表信息等)以及動態(tài)類型所實(shí)現(xiàn)的方法的信息,因此 iface 的第一個字段指向一個 itab 類型結(jié)構(gòu)。itab 結(jié)構(gòu)的定義如下:

// $GOROOT/src/runtime/runtime2.go
type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

這里我們也可以看到,itab 結(jié)構(gòu)中的第一個字段 inter 指向的 interfacetype 結(jié)構(gòu),存儲著這個接口類型自身的信息。你看一下下面這段代碼表示的 interfacetype 類型定義,這個 interfacetype 結(jié)構(gòu)由類型信息(typ)、包路徑名(pkgpath)和接口方法集合切片(mhdr)組成。

// $GOROOT/src/runtime/type.go
type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}

itab 結(jié)構(gòu)中的字段 _type 則存儲著這個接口類型變量的動態(tài)類型的信息,字段 fun 則是動態(tài)類型已實(shí)現(xiàn)的接口方法的調(diào)用地址數(shù)組。

下面我們再結(jié)合例子用圖片來直觀展現(xiàn) efaceiface 的結(jié)構(gòu)。首先我們看一個用 eface 表示的空接口類型變量的例子:

type T struct {
    n int
    s string
}

func main() {
    var t = T {
        n: 17,
        s: "hello, interface",
    }
    
    var ei interface{} = t // Go運(yùn)行時使用eface結(jié)構(gòu)表示ei
}

這個例子中的空接口類型變量 ei 在 Go 運(yùn)行時的表示是這樣的:

我們看到空接口類型的表示較為簡單,圖中上半部分 _type 字段指向它的動態(tài)類型 T 的類型信息,下半部分的 data 則是指向一個 T 類型的實(shí)例值。

我們再來看一個更復(fù)雜的用 iface 表示非空接口類型變量的例子:

type T struct {
    n int
    s string
}

func (T) M1() {}
func (T) M2() {}

type NonEmptyInterface interface {
    M1()
    M2()
}

func main() {
    var t = T{
        n: 18,
        s: "hello, interface",
    }
    var i NonEmptyInterface = t
}

eface 比起來,iface 的表示稍微復(fù)雜些。我也畫了一幅表示上面 NonEmptyInterface 接口類型變量在 Go 運(yùn)行時表示的示意圖:

由上面的這兩幅圖,我們可以看出,每個接口類型變量在運(yùn)行時的表示都是由兩部分組成的,針對不同接口類型我們可以簡化記作:eface(_type, data)iface(tab, data)

而且,雖然 efaceiface 的第一個字段有所差別,但 tab_type 可以統(tǒng)一看作是動態(tài)類型的類型信息。Go 語言中每種類型都會有唯一的 _type 信息,無論是內(nèi)置原生類型,還是自定義類型都有。Go 運(yùn)行時會為程序內(nèi)的全部類型建立只讀的共享 _type 信息表,因此擁有相同動態(tài)類型的同類接口類型變量的 _type/tab 信息是相同的。

而接口類型變量的 data 部分則是指向一個動態(tài)分配的內(nèi)存空間,這個內(nèi)存空間存儲的是賦值給接口類型變量的動態(tài)類型變量的值。未顯式初始化的接口類型變量的值為nil,也就是這個變量的 _type/tab 和 data 都為 nil。

也就是說,我們判斷兩個接口類型變量是否相等,只需判斷 _type/tab 以及 data 是否都相等即可。兩個接口變量的 _type/tab 不同時,即兩個接口變量的動態(tài)類型不相同時,兩個接口類型變量一定不等。

當(dāng)兩個接口變量的 _type/tab 相同時,對 data 的相等判斷要有區(qū)分。當(dāng)接口變量的動態(tài)類型為指針類型時 (*T),Go 不會再額外分配內(nèi)存存儲指針值,而會將動態(tài)類型的指針值直接存入 data 字段中,這樣 data 值的相等性決定了兩個接口類型變量是否相等;當(dāng)接口變量的動態(tài)類型為非指針類型 (T) 時,我們判斷的將不是 data 指針的值是否相等,而是判斷 data 指針指向的內(nèi)存空間所存儲的數(shù)據(jù)值是否相等,若相等,則兩個接口類型變量相等。

不過,通過肉眼去辨別接口類型變量是否相等總是困難一些,我們可以引入一些 helper 函數(shù)。借助這些函數(shù),我們可以清晰地輸出接口類型變量的內(nèi)部表示,這樣就可以一目了然地看出兩個變量是否相等了。

由于 efaceifaceruntime 包中的非導(dǎo)出結(jié)構(gòu)體定義,我們不能直接在包外使用,所以也就無法直接訪問到兩個結(jié)構(gòu)體中的數(shù)據(jù)。不過,Go 語言提供了 println 預(yù)定義函數(shù),可以用來輸出 efaceiface 的兩個指針字段的值。

在編譯階段,編譯器會根據(jù)要輸出的參數(shù)的類型將 println 替換為特定的函數(shù),這些函數(shù)都定義在 $GOROOT/src/runtime/print.go 文件中,而針對 efaceiface 類型的打印函數(shù)實(shí)現(xiàn)如下:

// $GOROOT/src/runtime/print.go
func printeface(e eface) {
    print("(", e._type, ",", e.data, ")")
}

func printiface(i iface) {
    print("(", i.tab, ",", i.data, ")")
}

我們看到,printefaceprintiface 會輸出各自的兩個指針字段的值。下面我們就來使用 println 函數(shù)輸出各類接口類型變量的內(nèi)部表示信息,并結(jié)合輸出結(jié)果,解析接口類型變量的等值比較操作。

第一種:nil 接口變量

我們知道,未賦初值的接口類型變量的值為 nil,這類變量也就是 nil 接口變量,我們來看這類變量的內(nèi)部表示輸出的例子:

func printNilInterface() {
  // nil接口變量
  var i interface{} // 空接口類型
  var err error     // 非空接口類型
  println(i)
  println(err)
  println("i = nil:", i == nil)
  println("err = nil:", err == nil)
  println("i = err:", i == err)
}

運(yùn)行這個函數(shù),輸出結(jié)果是這樣的:

(0x0,0x0)
(0x0,0x0)
i = nil: true
err = nil: true
i = err: true

我們看到,無論是空接口類型還是非空接口類型變量,一旦變量值為 nil,那么它們內(nèi)部表示均為 (0x0, 0x0),也就是類型信息、數(shù)據(jù)值信息均為空。因此上面的變量 ierr 等值判斷為 true

第二種:空接口類型變量

下面是空接口類型變量的內(nèi)部表示輸出的例子:

  func printEmptyInterface() {
      var eif1 interface{} // 空接口類型
      var eif2 interface{} // 空接口類型
      var n, m int = 17, 18
  
      eif1 = n
      eif2 = m

      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // false
  
      eif2 = 17
      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // true
 
      eif2 = int64(17)
      println("eif1:", eif1)
      println("eif2:", eif2)
      println("eif1 = eif2:", eif1 == eif2) // false
 }

這個例子的運(yùn)行輸出結(jié)果是這樣的:

eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0xc00007ef40)
eif1 = eif2: false
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0x10eb3d0)
eif1 = eif2: true
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac640,0x10eb3d8)
eif1 = eif2: false

我們按順序分析一下這個輸出結(jié)果。

首先,代碼執(zhí)行到第 11 行時,eif1eif2 已經(jīng)分別被賦值整型值 1718,這樣 eif1eif2 的動態(tài)類型的類型信息是相同的(都是 0x10ac580),但 data 指針指向的內(nèi)存塊中存儲的值不同,一個是 17,一個是 18,于是 eif1 不等于 eif2

接著,代碼執(zhí)行到第 16 行的時候,eif2 已經(jīng)被重新賦值為 17,這樣 eif1eif2 不僅存儲的動態(tài)類型的類型信息是相同的(都是 0x10ac580),data 指針指向的內(nèi)存塊中存儲值也相同了,都是 17,于是 eif1 等于 eif2

然后,代碼執(zhí)行到第 21 行時,eif2 已經(jīng)被重新賦值了 int64 類型的數(shù)值 17。這樣,eif1eif2 存儲的動態(tài)類型的類型信息就變成不同的了,一個是 int,一個是 int64,即便 data 指針指向的內(nèi)存塊中存儲值是相同的,最終 eif1eif2 也是不相等的。

第三種:非空接口類型變量

這里,我們也直接來看一個非空接口類型變量的內(nèi)部表示輸出的例子:

type T int

func (t T) Error() string { 
    return "bad error"
}

func printNonEmptyInterface() { 
    var err1 error // 非空接口類型
    var err2 error // 非空接口類型
    err1 = (*T)(nil)
    println("err1:", err1)
    println("err1 = nil:", err1 == nil)

    err1 = T(5)
    err2 = T(6)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)

    err2 = fmt.Errorf("%d\n", 5)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)
}   

這個例子的運(yùn)行輸出結(jié)果如下:

err1: (0x10ed120,0x0)
err1 = nil: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed1a0,0x10eb318)
err1 = err2: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed0c0,0xc000010050)
err1 = err2: false

我們看到上面示例中每一輪通過 println 輸出的 err1err2tabdata 值,要么 data 值不同,要么 tabdata 值都不同。

和空接口類型變量一樣,只有 tabdata 指的數(shù)據(jù)內(nèi)容一致的情況下,兩個非空接口類型變量之間才能劃等號。這里我們要注意 err1 下面的賦值情況:

err1 = (*T)(nil)

針對這種賦值,println 輸出的 err1 是(0x10ed120, 0x0),也就是非空接口類型變量的類型信息并不為空,數(shù)據(jù)指針為空,因此它與 nil0x0, 0x0)之間不能劃等號。

現(xiàn)在我們再回到我們開頭的那個問題,你是不是已經(jīng)豁然開朗了呢?開頭的問題中,從 returnsError 返回的 error 接口類型變量 err 的數(shù)據(jù)指針雖然為空,但它的類型信息(iface.tab)并不為空,而是 *MyError 對應(yīng)的類型信息,這樣 errnil0x0,0x0)相比自然不相等,這就是我們開頭那個問題的答案解析,現(xiàn)在你明白了嗎?

第四種:空接口類型變量與非空接口類型變量的等值比較

下面是非空接口類型變量和空接口類型變量之間進(jìn)行比較的例子:

func printEmptyInterfaceAndNonEmptyInterface() {
  var eif interface{} = T(5)
  var err error = T(5)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)

  err = T(6)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)
}

這個示例的輸出結(jié)果如下:

eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4d8)
eif = err: true
eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4e0)
eif = err: false

你可以看到,空接口類型變量和非空接口類型變量內(nèi)部表示的結(jié)構(gòu)有所不同(第一個字段:_type vs. tab),兩者似乎一定不能相等。但 Go 在進(jìn)行等值比較時,類型比較使用的是 eface_typeifacetab._type,因此就像我們在這個例子中看到的那樣,當(dāng) eiferr 都被賦值為 T(5) 時,兩者之間是劃等號的。

好了,到這里,我們已經(jīng)理解了各類接口類型變量在運(yùn)行時層的表示。我們可以通過 println 可以查看這個表示信息,從中我們也知道了接口變量只有在類型信息與值信息都一致的情況下才能劃等號。

五、輸出接口類型變量內(nèi)部表示的詳細(xì)信息

不過,println 輸出的接口類型變量的內(nèi)部表示信息,在一般情況下都是足夠的,但有些時候又顯得過于簡略,比如在上面最后一個例子中,如果僅憑 eif: (0x10b3b00,0x10eb4d0)err: (0x10ed380,0x10eb4d8) 的輸出,我們是無法想到兩個變量是相等的。

那這時如果我們能輸出接口類型變量內(nèi)部表示的詳細(xì)信息(比如:tab._type),那勢必可以取得事半功倍的效果。接下來我們就看看這要怎么做。

前面提到過,efaceiface 以及組成它們的 itab_type 都是 runtime 包下的非導(dǎo)出結(jié)構(gòu)體,我們無法在外部直接引用它們。但我們發(fā)現(xiàn),組成 efaceiface 的類型都是基本數(shù)據(jù)類型,我們完全可以通過“復(fù)制代碼”的方式將它們拿到 runtime 包外面來。

不過,這里要注意,由于 runtime 中的 efaceiface,或者它們的組成可能會隨著 Go 版本的變化發(fā)生變化,因此這個方法不具備跨版本兼容性。也就是說,基于 Go 1.17 版本復(fù)制的代碼,可能僅適用于使用 Go 1.17 版本編譯。這里我們就以 Go 1.17 版本為例看看:

// dumpinterface.go 
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type tflag uint8
type nameOff int32
type typeOff int32

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

... ...

const ptrSize = unsafe.Sizeof(uintptr(0))

func dumpEface(i interface{}) {
    ptrToEface := (*eface)(unsafe.Pointer(&i))
    fmt.Printf("eface: %+v\n", *ptrToEface)

    if ptrToEface._type != nil {
        // dump _type info
        fmt.Printf("\t _type: %+v\n", *(ptrToEface._type))
    }

    if ptrToEface.data != nil {
        // dump data
        switch i.(type) {
        case int:
            dumpInt(ptrToEface.data)
        case float64:
            dumpFloat64(ptrToEface.data)
        case T:
            dumpT(ptrToEface.data)

        // other cases ... ...
        default:
            fmt.Printf("\t unsupported data type\n")
        }
    }
    fmt.Printf("\n")
}

func dumpItabOfIface(ptrToIface unsafe.Pointer) {
    p := (*iface)(ptrToIface)
    fmt.Printf("iface: %+v\n", *p)

    if p.tab != nil {
        // dump itab
        fmt.Printf("\t itab: %+v\n", *(p.tab))
        // dump inter in itab
        fmt.Printf("\t\t inter: %+v\n", *(p.tab.inter))

        // dump _type in itab
        fmt.Printf("\t\t _type: %+v\n", *(p.tab._type))

        // dump fun in tab
        funPtr := unsafe.Pointer(&(p.tab.fun))
        fmt.Printf("\t\t fun: [")
        for i := 0; i < len((*(p.tab.inter)).mhdr); i++ {
            tp := (*uintptr)(unsafe.Pointer(uintptr(funPtr) + uintptr(i)*ptrSize))
            fmt.Printf("0x%x(%d),", *tp, *tp)
        }
        fmt.Printf("]\n")
    }
}

func dumpDataOfIface(i interface{}) {
    // this is a trick as the data part of eface and iface are same
    ptrToEface := (*eface)(unsafe.Pointer(&i))

    if ptrToEface.data != nil {
        // dump data
        switch i.(type) {
        case int:
            dumpInt(ptrToEface.data)
        case float64:
            dumpFloat64(ptrToEface.data)
        case T:
            dumpT(ptrToEface.data)

        // other cases ... ...

        default:
            fmt.Printf("\t unsupported data type\n")
        }
    }
    fmt.Printf("\n")
}

func dumpT(dataOfIface unsafe.Pointer) {
    var p *T = (*T)(dataOfIface)
    fmt.Printf("\t data: %+v\n", *p)
}
... ...

這里只挑選了關(guān)鍵部分,省略了部分代碼。上面這個 dumpinterface.go 中提供了三個主要函數(shù):

  • dumpEface: 用于輸出空接口類型變量的內(nèi)部表示信息;
  • dumpItabOfIface: 用于輸出非空接口類型變量的 tab 字段信息;
  • dumpDataOfIface: 用于輸出非空接口類型變量的 data 字段信息;

我們利用這三個函數(shù)來輸出一下前面 printEmptyInterfaceAndNonEmptyInterface 函數(shù)中的接口類型變量的信息:

package main

import "unsafe"

type T int

func (t T) Error() string {
    return "bad error"
}

func main() {
    var eif interface{} = T(5)
    var err error = T(5)
    println("eif:", eif)
    println("err:", err)
    println("eif = err:", eif == err)
    
    dumpEface(eif)
    dumpItabOfIface(unsafe.Pointer(&err))
    dumpDataOfIface(err)
}

運(yùn)行這個示例代碼,我們得到了這個輸出結(jié)果:

eif: (0x10b38c0,0x10e9b30)
err: (0x10eb690,0x10e9b30)
eif = err: true
eface: {_type:0x10b38c0 data:0x10e9b30}
   _type: {size:8 ptrdata:0 hash:1156555957 tflag:15 align:8 fieldAlign:8 kind:2 equal:0x10032e0 gcdata:0x10e9a60 str:4946 ptrToThis:58496}
   data: bad error

iface: {tab:0x10eb690 data:0x10e9b30}
   itab: {inter:0x10b5e20 _type:0x10b38c0 hash:1156555957 _:[0 0 0 0] fun:[17454976]}
     inter: {typ:{size:16 ptrdata:16 hash:235953867 tflag:7 align:8 fieldAlign:8 kind:20 equal:0x10034c0 gcdata:0x10d2418 str:3666 ptrToThis:26848} pkgpath:{bytes:<nil>} mhdr:[{name:2592 ityp:43520}]}
     _type: {size:8 ptrdata:0 hash:1156555957 tflag:15 align:8 fieldAlign:8 kind:2 equal:0x10032e0 gcdata:0x10e9a60 str:4946 ptrToThis:58496}
     fun: [0x10a5780(17454976),]
   data: bad error

從輸出結(jié)果中,我們看到 eif_type0x10b38c0)與 errtab._type0x10b38c0)是一致的,data 指針?biāo)竷?nèi)容(“bad error”)也是一致的,因此 eif == err 表達(dá)式的結(jié)果為 true

再次強(qiáng)調(diào)一遍,上面這個實(shí)現(xiàn)可能僅在 Go 1.17 版本上測試通過,并且在輸出 ifaceefacedata 部分內(nèi)容時只列出了 intfloat64T 類型的數(shù)據(jù)讀取實(shí)現(xiàn),沒有列出全部類型的實(shí)現(xiàn),你可以根據(jù)自己的需要實(shí)現(xiàn)其余數(shù)據(jù)類型。dumpinterface.go 的完整代碼你可以在這里找到。

我們現(xiàn)在已經(jīng)知道了,接口類型有著復(fù)雜的內(nèi)部結(jié)構(gòu),所以我們將一個類型變量值賦值給一個接口類型變量值的過程肯定不會像 var i int = 5 那么簡單,那么接口類型變量賦值的過程是怎樣的呢?其實(shí)接口類型變量賦值是一個“裝箱”的過程。

六、接口類型的裝箱(boxing)原理

裝箱(boxing)是編程語言領(lǐng)域的一個基礎(chǔ)概念,一般是指把一個值類型轉(zhuǎn)換成引用類型,比如在支持裝箱概念的 Java 語言中,將一個 int 變量轉(zhuǎn)換成 Integer 對象就是一個裝箱操作。

在 Go 語言中,將任意類型賦值給一個接口類型變量也是裝箱操作。有了前面對接口類型變量內(nèi)部表示的學(xué)習(xí),我們知道接口類型的裝箱實(shí)際就是創(chuàng)建一個 efaceiface 的過程。接下來我們就來簡要描述一下這個過程,也就是接口類型的裝箱原理。

我們基于下面這個例子中的接口裝箱操作來說明:

// interface_internal.go

  type T struct {
      n int
      s string
  }
  
  func (T) M1() {}
  func (T) M2() {}
  
  type NonEmptyInterface interface {
      M1()
      M2()
  }
  
  func main() {
      var t = T{
          n: 17,
          s: "hello, interface",
      }
      var ei interface{}
      ei = t
 
      var i NonEmptyInterface
      i = t
      fmt.Println(ei)
      fmt.Println(i)
  }

這個例子中,對 eii 兩個接口類型變量的賦值都會觸發(fā)裝箱操作,要想知道 Go 在背后做了些什么,我們需要“下沉”一層,也就是要輸出上面 Go 代碼對應(yīng)的匯編代碼:

$go tool compile -S interface_internal.go > interface_internal.s

對應(yīng) ei = t 一行的匯編如下:

    0x0026 00038 (interface_internal.go:24) MOVQ    $17, ""..autotmp_15+104(SP)
    0x002f 00047 (interface_internal.go:24) LEAQ    go.string."hello, interface"(SB), CX
    0x0036 00054 (interface_internal.go:24) MOVQ    CX, ""..autotmp_15+112(SP)
    0x003b 00059 (interface_internal.go:24) MOVQ    $16, ""..autotmp_15+120(SP)
    0x0044 00068 (interface_internal.go:24) LEAQ    type."".T(SB), AX
    0x004b 00075 (interface_internal.go:24) LEAQ    ""..autotmp_15+104(SP), BX
    0x0050 00080 (interface_internal.go:24) PCDATA  $1, $0
    0x0050 00080 (interface_internal.go:24) CALL    runtime.convT2E(SB)

對應(yīng) i = t 一行的匯編如下:

    0x005f 00095 (interface_internal.go:27) MOVQ    $17, ""..autotmp_15+104(SP)
    0x0068 00104 (interface_internal.go:27) LEAQ    go.string."hello, interface"(SB), CX
    0x006f 00111 (interface_internal.go:27) MOVQ    CX, ""..autotmp_15+112(SP)
    0x0074 00116 (interface_internal.go:27) MOVQ    $16, ""..autotmp_15+120(SP)
    0x007d 00125 (interface_internal.go:27) LEAQ    go.itab."".T,"".NonEmptyInterface(SB), AX
    0x0084 00132 (interface_internal.go:27) LEAQ    ""..autotmp_15+104(SP), BX
    0x0089 00137 (interface_internal.go:27) PCDATA  $1, $1
    0x0089 00137 (interface_internal.go:27) CALL    runtime.convT2I(SB)

在將動態(tài)類型變量賦值給接口類型變量語句對應(yīng)的匯編代碼中,我們看到了 convT2EconvT2I 兩個 runtime 包的函數(shù)。這兩個函數(shù)的實(shí)現(xiàn)位于 $GOROOT/src/runtime/iface.go 中:

// $GOROOT/src/runtime/iface.go

func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
    if raceenabled {
        raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
    }
    if msanenabled {
        msanread(elem, t.size)
    }
    x := mallocgc(t.size, t, true)
    typedmemmove(t, x, elem)
    e._type = t
    e.data = x
    return
}

func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
    t := tab._type
    if raceenabled {
        raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
    }
    if msanenabled {
        msanread(elem, t.size)
    }
    x := mallocgc(t.size, t, true)
    typedmemmove(t, x, elem)
    i.tab = tab
    i.data = x
    return
}

convT2E 用于將任意類型轉(zhuǎn)換為一個 efaceconvT2I 用于將任意類型轉(zhuǎn)換為一個 iface。兩個函數(shù)的實(shí)現(xiàn)邏輯相似,主要思路就是根據(jù)傳入的類型信息(convT2E_typeconvT2Itab._type)分配一塊內(nèi)存空間,并將 elem 指向的數(shù)據(jù)拷貝到這塊內(nèi)存空間中,最后傳入的類型信息作為返回值結(jié)構(gòu)中的類型信息,返回值結(jié)構(gòu)中的數(shù)據(jù)指針(data)指向新分配的那塊內(nèi)存空間。

由此我們也可以看出,經(jīng)過裝箱后,箱內(nèi)的數(shù)據(jù),也就是存放在新分配的內(nèi)存空間中的數(shù)據(jù)與原變量便無瓜葛了,比如下面這個例子:

func main() {
  var n int = 61
  var ei interface{} = n
  n = 62  // n的值已經(jīng)改變
  fmt.Println("data in box:", ei) // 輸出仍是61
}

那么 convT2EconvT2I 函數(shù)的類型信息是從何而來的呢?

其實(shí)這些都依賴 Go 編譯器的工作。編譯器知道每個要轉(zhuǎn)換為接口類型變量(toType)和動態(tài)類型變量的類型(fromType),它會根據(jù)這一對類型選擇適當(dāng)?shù)?convT2X 函數(shù),并在生成代碼時使用選出的 convT2X 函數(shù)參與裝箱操作。

不過,裝箱是一個有性能損耗的操作,因此 Go 也在不斷對裝箱操作進(jìn)行優(yōu)化,包括對常見類型如整型、字符串、切片等提供系列快速轉(zhuǎn)換函數(shù):

// $GOROOT/src/runtime/iface.go
func convT16(val any) unsafe.Pointer     // val must be uint16-like
func convT32(val any) unsafe.Pointer     // val must be uint32-like
func convT64(val any) unsafe.Pointer     // val must be uint64-like
func convTstring(val any) unsafe.Pointer // val must be a string
func convTslice(val any) unsafe.Pointer  // val must be a slice

這些函數(shù)去除了 typedmemmove 操作,增加了零值快速返回等特性。

同時 Go 建立了 staticuint64s 區(qū)域,對 255 以內(nèi)的小整數(shù)值進(jìn)行裝箱操作時不再分配新內(nèi)存,而是利用 staticuint64s 區(qū)域的內(nèi)存空間,下面是 staticuint64s 的定義:

// $GOROOT/src/runtime/iface.go
// staticuint64s is used to avoid allocating in convTx for small integer values.
var staticuint64s = [...]uint64{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  ... ...
}

七、小結(jié)

接口類型作為參與構(gòu)建 Go 應(yīng)用骨架的重要參與者,在 Go 語言中有著很高的地位。它這個地位的取得離不開它擁有的“動靜兼?zhèn)洹钡恼Z法特性。Go 接口的動態(tài)特性讓 Go 擁有與動態(tài)語言相近的靈活性,而靜態(tài)特性又在編譯階段保證了這種靈活性的安全。

要更好地理解 Go 接口的這兩種特性,我們需要深入到 Go 接口在運(yùn)行時的表示層面上去。接口類型變量在運(yùn)行時表示為 efaceifaceeface 用于表示空接口類型變量,iface 用于表示非空接口類型變量。只有兩個接口類型變量的類型信息(eface._type/iface.tab._type)相同,且數(shù)據(jù)指針(eface.data/iface.data)所指數(shù)據(jù)相同時,兩個接口類型變量才是相等的。

我們可以通過 println 輸出接口類型變量的兩部分指針變量的值。而且,通過拷貝 runtimeefaceiface 相關(guān)類型源碼,我們還可以自定義輸出 eface/iface 詳盡信息的函數(shù),不過要注意的是,由于 runtime 層代碼的演進(jìn),這個函數(shù)可能不具備在 Go 版本間的移植性。

最后,接口類型變量的賦值本質(zhì)上是一種裝箱操作,裝箱操作是由 Go 編譯器和運(yùn)行時共同完成的,有一定的性能開銷,對于性能敏感的系統(tǒng)來說,我們應(yīng)該盡量避免或減少這類裝箱操作。

總結(jié)

以上是生活随笔為你收集整理的Go 接口:nil接口为什么不等于nil?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

激情文学丁香 | 久草视频网 | www.狠狠插.com | 成人小视频在线免费观看 | 亚色视频在线观看 | 91精选 | 亚洲精品国产精品久久99热 | 欧美在线观看小视频 | 丁香激情综合国产 | 日本中文字幕在线一区 | 日韩免费电影网站 | 欧美精品久久久久久久久久白贞 | 色姑娘综合天天 | 国产视频一区在线免费观看 | 国产精品九色 | 国产日韩高清在线 | 国产精品一码二码三码在线 | 中文字幕一区二区在线播放 | 精品影院一区二区久久久 | 久久精品国产成人精品 | 欧美精品v国产精品v日韩精品 | 永久免费看av | 超碰97人人在线 | 中文字幕视频网 | 国产免费人成xvideos视频 | 亚洲午夜不卡 | 99免费在线播放99久久免费 | 国产片免费在线观看视频 | 亚洲最大的av网站 | 99精品国产兔费观看久久99 | 久久久久国产成人精品亚洲午夜 | 黄色国产高清 | 亚洲欧美在线视频免费 | 中文在线中文a | 国产黄色精品在线观看 | 亚洲干视频在线观看 | 天天干天天拍天天操 | 蜜臀av性久久久久av蜜臀妖精 | 亚洲精品裸体 | 欧美国产精品一区二区 | 美女网站色免费 | 中文一区在线观看 | 这里有精品在线视频 | 国产精品久久久久久久免费 | 色先锋av资源中文字幕 | 久草在线视频在线 | 国产精品二区三区 | av千婊在线免费观看 | 六月天综合网 | 在线观看91精品国产网站 | 人人澡超碰碰97碰碰碰软件 | 国产精品久久久视频 | 精品国产99国产精品 | 午夜精品久久久久久 | 久久大片网站 | 国内精品小视频 | 国产毛片在线 | 亚洲黄a | 欧美a级片免费看 | 天天摸天天舔天天操 | 亚洲一区日韩精品 | 中文字幕美女免费在线 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 久久久久久久久久久久电影 | 国产毛片在线 | 欧美日韩不卡一区 | 久久久久久蜜av免费网站 | 色橹橹欧美在线观看视频高清 | 久久综合九色综合欧美狠狠 | 免费 在线 中文 日本 | 日韩欧美在线中文字幕 | 香蕉97视频观看在线观看 | 国产亚洲免费的视频看 | 国产高清视频在线观看 | 91麻豆精品国产91久久久使用方法 | 丁香花中文字幕 | 777视频在线观看 | 在线中文字幕视频 | 18国产精品白浆在线观看免费 | 99精彩视频在线观看免费 | 国产精品嫩草影视久久久 | 亚洲黄色片在线 | 丁香婷婷久久 | 天天操天天摸天天干 | 99热在 | 亚洲人成人99网站 | 久草视频免费在线播放 | 中文字幕一区二区三区在线观看 | 国产美女精品 | 操操操天天操 | 久久免费在线 | 91精品视频一区二区三区 | 国产va饥渴难耐女保洁员在线观看 | 久久综合九色99 | 四虎影视成人精品 | 香蕉视频在线播放 | 婷婷激情久久 | 国产成人亚洲在线电影 | 天天综合入口 | 婷婷狠狠操 | av三级av| 亚洲精品在线国产 | 91亚洲夫妻 | 久久99精品波多结衣一区 | 综合在线色 | 波多野结衣一区二区三区中文字幕 | 久久婷婷久久 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 日韩| 中文字幕一二三区 | 香蕉视频在线视频 | 久草97| 最新国产一区二区三区 | 精品一区二区三区香蕉蜜桃 | 九九免费在线观看 | 日韩在线网址 | 91经典在线 | 国产精品黑丝在线观看 | 国产成人99av超碰超爽 | 啪啪激情网 | 手机看片福利 | 黄网站色成年免费观看 | 久久国产美女视频 | 久久久久综合精品福利啪啪 | 亚洲国产电影在线观看 | 丁香六月在线 | www国产亚洲精品久久麻豆 | 日韩av成人免费看 | 国产99久久久国产精品免费看 | 狠狠色狠狠色综合日日小说 | 亚洲欧美精品在线 | 亚洲乱码在线 | 五月天九九 | 久草在线在线精品观看 | 天天干天天看 | 国产精品自产拍在线观看中文 | 久操中文字幕在线观看 | 免费看短 | 久久99精品久久只有精品 | 国产 日韩 在线 亚洲 字幕 中文 | 久久激情视频 | 伊人射 | 91九色九色| 国产最新视频在线 | 一本—道久久a久久精品蜜桃 | 亚洲精品久 | 国产色视频一区二区三区qq号 | 最近能播放的中文字幕 | 欧美大片大全 | 中文字幕在线免费观看视频 | aaa毛片视频 | 久久精品艹 | 成人精品国产免费网站 | 狠狠色综合网站久久久久久久 | 97电影院网| 91亚洲精品久久久 | 国内精品久久久久久久久久 | 国产操在线| 免费欧美| 激情在线网址 | 免费观看一区二区 | 国产精品久久久久久久久搜平片 | 狠狠操欧美 | 欧美五月婷婷 | 日韩精品一区二区免费视频 | 久久久精品福利视频 | 99久久国产免费,99久久国产免费大片 | 精品国产一区二区三区男人吃奶 | 午夜在线免费视频 | 国产又粗又猛又黄又爽 | 性色av免费观看 | 91精品欧美一区二区三区 | 免费激情在线电影 | 日韩精品免费在线视频 | 国产99久久精品一区二区300 | 国产精品国产毛片 | 久久深夜| av电影免费看 | 草久草久| 少妇搡bbbb搡bbb搡忠贞 | 99精品国产视频 | 丝袜制服综合网 | 日韩一区二区免费在线观看 | 免费福利视频导航 | 深爱激情五月网 | 国产手机精品视频 | 91视频高清完整版 | 午夜影院先 | 久久这里只有精品首页 | 精品久久91 | 久久久影视 | 久草免费新视频 | 黄色a在线 | 一级欧美黄 | 精品一区二区在线免费观看 | 91超碰在线播放 | 免费aa大片 | 久久免费在线观看 | 国产精品不卡在线观看 | 国产亚洲人成网站在线观看 | 国产va饥渴难耐女保洁员在线观看 | 日韩精品视频免费看 | 久久久久久综合网天天 | 色偷偷人人澡久久超碰69 | 9久久精品 | 国产视频久 | www色网站 | 国产亲近乱来精品 | 日韩视频一区二区在线 | 国产成人久久精品77777 | 精品视频免费久久久看 | 国产精品欧美久久 | 亚洲国产综合在线 | 波多野结衣动态图 | 国内久久精品视频 | 99精品在线观看视频 | 欧美精品二 | 久久电影日韩 | 国产一级片直播 | av资源在线观看 | 国产成人av综合色 | 91试看| 奇米影视在线99精品 | 婷婷丁香激情 | 国产小视频在线免费观看视频 | 久久九九久久 | 西西大胆免费视频 | 亚洲干 | 在线中文字幕av观看 | 一区中文字幕 | 激情 一区二区 | 亚洲男人天堂2018 | 亚洲资源网 | 91精品视频观看 | 国产精品毛片久久久久久久 | 在线观看韩日电影免费 | 亚洲国产精品500在线观看 | 国产一区二区三区黄 | 亚洲区视频在线观看 | 欧美日韩首页 | 国产视频一区精品 | 亚州av一区 | a在线播放| 国产黄色在线 | 久草新在线 | 久久丁香网 | 久久激情视频网 | 国产69久久精品成人看 | 日韩在线观看你懂的 | 国产91亚洲精品 | 亚洲精品国产综合久久 | 亚洲国产欧美一区二区三区丁香婷 | 免费一级特黄毛大片 | 9色在线视频 | 久久免费av | 国产电影黄色av | 婷婷激情小说网 | 97av影院| 日韩一区二区三区在线看 | 一区二区在线影院 | 人人爽人人爽 | 欧美日韩xxx | 久久久网站 | 久久久穴 | 国产精品mm | 国产欧美精品一区二区三区四区 | 久久99久久99| 免费看一级片 | 精品久久久久免费极品大片 | 夜色资源网| 欧美精品久久久久久久亚洲调教 | 亚洲性xxxx | 国产区欧美 | 久久久久9999亚洲精品 | 在线观看国产福利片 | 天天摸天天弄 | 91久久久久久久 | 99超碰在线观看 | 久草视频免费在线播放 | 国产无套精品久久久久久 | 亚洲成av人片在线观看 | 欧美精品在线观看 | 日韩精品一区在线观看 | av免费在线观看网站 | 日韩免费中文字幕 | 久久免费片 | 99r国产精品 | 久久国产精品视频 | 亚洲国产三级在线 | 久久免费福利 | 精品乱码一区二区三四区 | 日本天天操 | 97手机电影网 | 中文成人字幕 | 波多野结衣网址 | 九九在线免费视频 | 麻豆久久精品 | 91麻豆精品一区二区三区 | 亚洲激情小视频 | 国产精品毛片一区视频播 | 福利视频入口 | 超碰在线官网 | 又黄又爽又色无遮挡免费 | 亚洲成人欧美 | 国产精品一区二区三区久久 | 久久久久久免费 | 国产高清在线视频 | 黄色大片网| 精品三级av | 国产黄色片网站 | 国产成人一区二区在线观看 | 色偷偷88888欧美精品久久久 | 亚洲日本一区二区在线 | 国产精品麻 | 在线播放视频一区 | 国产成在线观看免费视频 | 一区二区三区精品久久久 | 玖玖在线视频观看 | 四虎成人在线 | 久久国产精品久久精品 | 丁香激情综合国产 | 黄色软件在线观看视频 | 探花视频在线观看免费版 | 国产原创中文在线 | 日韩三级视频在线观看 | 国产免费美女 | 日韩极品视频在线观看 | 91精品蜜桃| 免费看亚洲毛片 | 日韩在线第一 | 国产 日韩 在线 亚洲 字幕 中文 | 久久在线 | 五月天激情视频 | 亚洲天堂在线观看完整版 | 欧美二区在线播放 | 中文字幕精品一区 | 欧美成亚洲 | av不卡在线看 | 91热视频| 色网影音先锋 | 精品国偷自产在线 | 午夜精品久久久久久中宇69 | 免费特级黄毛片 | 日韩精品第一区 | www.久久爱.cn | 久久午夜精品影院一区 | 日韩国产精品久久久久久亚洲 | 欧美a影视 | 一区二区三区免费 | 成人超碰97 | 国产色女 | 欧美a在线看 | 97视频免费看 | 偷拍福利视频一区二区三区 | 欧亚日韩精品一区二区在线 | 亚洲三区在线 | 日韩免费电影 | 一区二区视频免费在线观看 | 亚洲精品视频中文字幕 | 四虎影视成人永久免费观看视频 | 亚洲精品视频二区 | 国产精品在线看 | 奇米影视777影音先锋 | 天堂入口网站 | 国产精品第2页 | 欧美成人精品欧美一级乱黄 | 中文字幕免费 | 欧美a级在线播放 | 色多多视频在线观看 | 国产精品国产三级国产aⅴ入口 | 在线视频 影院 | 亚洲激情 欧美激情 | av线上看 | 91在线蜜桃臀 | 色黄www小说| 在线观看免费av片 | 国产精品女主播一区二区三区 | 国产精品99精品 | 色婷婷激情网 | 一区二区三区久久 | 一本一本久久aa综合精品 | 久久伊人八月婷婷综合激情 | 国产高清视频在线 | 欧美影片 | 亚洲国产日韩精品 | 一区二区视频在线观看免费 | 99久久久免费视频 | 99久久99视频只有精品 | 日韩电影一区二区在线观看 | 久久国色夜色精品国产 | 热久久免费视频 | av在线中文 | 1000部国产精品成人观看 | 国产精品高潮呻吟久久久久 | 狠狠88综合久久久久综合网 | 亚洲国产一区二区精品专区 | 特级a毛片| 超碰人人射 | 亚洲色图 校园春色 | 偷拍福利视频一区二区三区 | 九九免费在线看完整版 | 97在线视频免费看 | 97视频在线观看播放 | 91精品专区 | 久久天堂网站 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 久久国产电影院 | 国产韩国日本高清视频 | 国产日韩在线观看一区 | 在线观看www视频 | 久久精品成人热国产成 | 久久午夜精品视频 | 欧美最新大片在线看 | 77国产精品 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 福利视频一区二区 | 狠狠狠操| 深爱激情av | 97成人啪啪网 | 四虎8848免费高清在线观看 | 九九交易行官网 | 香蕉色综合 | 欧美特一级片 | 午夜少妇一区二区三区 | 亚洲尺码电影av久久 | 国产亚洲视频系列 | 国产精品女同一区二区三区久久夜 | 欧美成人区 | 亚洲欧美国产精品va在线观看 | 99久久夜色精品国产亚洲96 | 日日干激情五月 | 久久精品欧美日韩精品 | 成人av久久| av在线进入 | 精品夜夜嗨av一区二区三区 | 日韩精品第1页 | 国产精品女同一区二区三区久久夜 | 一区在线播放 | 欧美高清视频不卡网 | 久久久黄视频 | 国产精品久久久电影 | 特级毛片爽www免费版 | 99国产精品久久久久久久久久 | 午夜手机电影 | 美女视频黄色免费 | 天天色天天操综合 | 国产理论影院 | 丁香五香天综合情 | 亚洲精品网站在线 | 2021久久 | 国产一区二区不卡在线 | 国产短视频在线播放 | 亚洲视频久久 | 日韩欧美综合视频 | 99精品免费久久久久久久久日本 | 99久久精品免费看 | 97成人在线 | 亚洲综合黄色 | 超碰97国产精品人人cao | 国产成人在线网站 | 五月天国产精品 | 亚洲日本va在线观看 | 免费看黄在线观看 | 欧美日韩高清不卡 | 韩日三级在线 | 手机在线中文字幕 | 久草在线中文视频 | 99视频网站 | 不卡av电影在线 | 亚洲激情 欧美激情 | 午夜私人影院 | 久久福利小视频 | 久久久69 | 91成人免费看 | 国产在线探花 | 中文字幕专区高清在线观看 | www.狠狠色| 日本一区二区免费在线观看 | 久久国产电影院 | 91在线观看视频网站 | 日韩三级.com | 精品国产99国产精品 | 在线岛国av | 精品国产亚洲一区二区麻豆 | 精品欧美一区二区精品久久 | 中文字幕文字幕一区二区 | 欧美精品久久久久久久久久丰满 | 五月婷婷伊人网 | 不卡av电影在线 | 国产九色视频在线观看 | 国产99久久精品一区二区300 | 日韩成人免费电影 | 日本黄色免费网站 | 亚洲片在线 | 五月激情综合婷婷 | 免费视频资源 | 国产三级午夜理伦三级 | 日韩免费小视频 | 在线观看黄网 | 天天综合入口 | 伊人久久精品久久亚洲一区 | 国产二区视频在线观看 | 免费观看一级一片 | 欧美成人性战久久 | 免费网站观看www在线观看 | 91精品国产91p65 | 午夜视频一区二区三区 | 亚洲aⅴ久久精品 | 伊人天天狠天天添日日拍 | 婷婷激情在线 | 日韩av电影免费观看 | 五月天婷婷免费视频 | 亚洲综合最新在线 | 久久国产精品99精国产 | 五月天综合色激情 | 激情久久一区二区三区 | av高清影院| 国产手机视频精品 | 在线最新av | 久久er99热精品一区二区三区 | 国产经典 欧美精品 | 2024国产精品视频 | 波多野结衣精品 | 涩涩在线| 手机看片久久 | 99电影| 久久国内视频 | www国产亚洲精品久久网站 | 色婷婷狠狠操 | 国产免费黄色 | 国产三级精品在线 | 成人一级视频在线观看 | 国产精品一区欧美 | 久久99爱视频 | 久99久在线视频 | 美女网站黄免费 | av网址最新 | 五月天伊人网 | 久久久国产影视 | 人人爽人人干 | 国产成人av电影 | av电影免费观看 | 综合婷婷久久 | 日韩免费在线观看视频 | 美女视频黄免费的 | 亚洲电影久久久 | 狠狠操综合网 | 欧美日韩伦理一区 | 亚洲视频久久久久 | 不卡av在线 | 国内成人综合 | 国产亚洲精品久久久久秋 | 欧美a在线免费观看 | 在线观看久 | 美女视频黄免费的久久 | 蜜桃av观看 | 欧美午夜a | 色五月激情五月 | 成人在线视频你懂的 | 二区三区精品 | 免费看国产曰批40分钟 | 色婷婷综合久色 | 久久人人爽爽人人爽人人片av | 国产精品高清在线 | 国产亚洲成人网 | 激情开心色| 91视频com | 久久久久久久久久久影院 | 日本久久电影 | 九九精品视频在线观看 | 久久精品久久精品久久 | 亚洲精品欧洲精品 | 特级片免费看 | 亚洲精品视频偷拍 | 日韩一区二区三区不卡 | 日本爱爱免费视频 | 一级片免费视频 | 久久 地址 | 久久视讯 | 91精品办公室少妇高潮对白 | 一区二区免费不卡在线 | 一区二区欧美激情 | 亚洲精品免费在线观看 | 香蕉视频4aa | 天天se天天cao天天干 | 欧美色图视频一区 | 国产在线999 | 久久精品4 | 丁香花在线视频观看免费 | 久久国产区 | 99在线热播 | 99久久精品午夜一区二区小说 | 在线免费观看视频a | 久久久久久久久久亚洲精品 | 99久精品视频 | 成人av在线直播 | 国产精品区在线观看 | 国产午夜影院 | 99精品亚洲 | 91视频传媒| 日韩高清在线一区二区 | 中文字幕视频网站 | 又色又爽又黄高潮的免费视频 | 成人av日韩 | 日本成址在线观看 | 97视频播放 | 91麻豆精品国产91久久久无需广告 | 亚洲黄色区 | 欧美精品久久99 | 日韩一区二区三免费高清在线观看 | 欧美日韩综合在线 | 日韩在线免费视频 | 国产日韩欧美中文 | 99精品国产兔费观看久久99 | 国产精品h在线观看 | 久久成人高清 | 国产中文字幕视频在线观看 | 国产精品99久久久久久久久 | 国内一级片在线观看 | 99久久精品电影 | 玖玖视频免费在线 | 亚洲高清视频在线观看 | 亚洲最新av在线 | 色a网| 国产午夜精品av一区二区 | 国产在线精品视频 | 久久久久免费精品国产小说色大师 | 成年免费在线视频 | 色婷婷视频网 | 亚洲天堂精品视频在线观看 | 国产黄色在线看 | 最近最新mv字幕免费观看 | 在线观看免费91 | 黄www在线观看 | 在线免费色| 成人免费观看av | 久久99久久99精品 | 欧美午夜精品久久久久久浪潮 | 日韩电影中文字幕 | 99久久这里只有精品 | 亚洲黄色在线观看 | 热久久精品在线 | 国产成人精品国内自产拍免费看 | 丁香九月激情 | 97在线看| 中文字幕在线观看免费高清电影 | 99综合电影在线视频 | 日本丶国产丶欧美色综合 | 日韩激情视频 | a在线v| 高清av在线免费观看 | 色99导航 | 在线观看久草 | 99久久久久久久久久 | 成人av影院在线观看 | 久久免费视频一区 | 国产精品免费在线播放 | 在线超碰av | av短片在线观看 | 美女性爽视频国产免费app | 日韩国产精品久久久久久亚洲 | 91麻豆精品国产自产在线游戏 | 亚洲精品午夜久久久 | 久久精品一区二区三区中文字幕 | 91九色成人 | 精品国产一区二区三区噜噜噜 | 中文字幕国产一区 | 午夜精品久久久久久久爽 | 久久成人欧美 | 久久精品中文字幕少妇 | 日韩伦理一区二区三区av在线 | 日韩欧美在线中文字幕 | 国产日韩欧美视频在线观看 | 亚洲日本在线视频观看 | 日韩免费一区 | 黄色的网站在线 | 亚州视频在线 | 日韩在线视频一区 | 国产精品久久久久久久久久尿 | 国产在线视频一区二区 | 久久久精品在线观看 | 久久久久久久久久久综合 | 欧美一级黄大片 | 中文字幕高清在线 | 久久99久久99精品免视看婷婷 | 久久久高清一区二区三区 | 精品久久久久久综合 | 欧美专区国产专区 | 亚洲三级在线免费观看 | 91精品1区2区 | 日韩av电影免费在线观看 | 91九色国产蝌蚪 | 亚洲第二色 | 美女网站视频免费黄 | 久精品一区 | 久久精品久久99精品久久 | 特黄免费av | 五月的婷婷 | 久久色视频 | 国产中出在线观看 | 午夜色大片在线观看 | 日本高清xxxx | 麻豆视频成人 | 91视频久久久久久 | 手机av在线网站 | 亚洲欧美日韩国产一区二区 | 久久福利小视频 | 全久久久久久久久久久电影 | 国产福利一区二区三区视频 | 日本女人的性生活视频 | 在线国产视频观看 | 亚洲理论视频 | 国产精品一区二区你懂的 | 在线不卡视频 | 国产精成人品免费观看 | 色婷av| 国产精品永久久久久久久www | 国产亚洲一区二区在线观看 | 久久视频这里只有精品 | 久久五月婷婷综合 | 五月天最新网址 | 91自拍视频在线观看 | av中文字幕在线观看网站 | 狠狠色丁香久久婷婷综合五月 | 久久久69 | 天天干天天在线 | 新av在线| 丰满少妇对白在线偷拍 | 免费美女久久99 | 免费av看片 | 激情电影影院 | 天天爽天天碰狠狠添 | 中文字幕在线乱 | 久久免费毛片视频 | 亚洲欧美日韩精品久久奇米一区 | 天堂在线视频免费观看 | 亚洲欧美在线视频免费 | 视频一区视频二区在线观看 | 91天堂素人约啪 | 在线播放国产一区二区三区 | 中文字幕在线高清 | 99在线热播精品免费 | 婷婷在线看 | 日韩在线一区二区免费 | 在线观看免费国产小视频 | 久久精品国产亚洲精品2020 | 在线观看精品一区 | 久久久久久久久久久久久久电影 | 国产探花| 麻豆传媒视频观看 | 黄色a视频 | 久久亚洲影院 | 啪啪免费视频网站 | www.久艹 | 五月天av在线 | 亚洲免费永久精品国产 | 天天干人人| 免费观看性生交 | 成人av午夜 | 欧美黑吊大战白妞欧美 | 欧美一区二区三区在线视频观看 | 欧美视频在线观看免费网址 | www.色com| 日韩一级电影在线观看 | 看片在线亚洲 | 久久免费激情视频 | 中文国产在线观看 | 国产精品美女免费 | 国产一二三区在线观看 | 久久99国产精品免费网站 | 日韩系列 | 99re久久精品国产 | 欧美色图亚洲图片 | 国内丰满少妇猛烈精品播 | 欧美一区二区免费在线观看 | 亚洲电影久久 | 五月天天色 | 国产高清日韩 | 中文字幕在线观看你懂的 | 成人亚洲精品久久久久 | 亚洲美女在线国产 | 日韩在线视频免费看 | www.久久婷婷 | 草久在线 | 97精品视频在线播放 | 亚洲日日射 | 久日精品 | 免费视频资源 | 免费看av在线 | 久久国内免费视频 | 安徽妇搡bbbb搡bbbb | 免费在线观看一区 | 综合久色| 亚洲另类人人澡 | 免费在线观看成人小视频 | 一级欧美日韩 | 亚洲免费精品视频 | 欧美中文字幕久久 | 国产成人性色生活片 | 国产视频久久久 | 亚洲丝袜中文 | 九九久久在线看 | 国产精品女同一区二区三区久久夜 | 草久视频在线 | 91精品久久久久久粉嫩 | 日韩v在线| 国产成年免费视频 | 国产在线免费观看 | 99久久99久久精品国产片 | 91中文字幕在线视频 | 99中文字幕 | www日韩精品 | 成年人在线看片 | 国产成人精品免高潮在线观看 | av在线不卡观看 | 成人免费视频视频在线观看 免费 | 999超碰| 中文视频在线播放 | 成人黄视频 | 99久久精品电影 | 中文字幕av在线免费 | av线上免费看 | 久久成年视频 | 久久国产精品99久久人人澡 | 国语麻豆 | 国产一级免费视频 | 涩五月婷婷 | 1024在线看片 | 欧美精品一二三 | 中文字幕一区二区三区久久 | 欧美精选一区二区三区 | 日韩a级免费视频 | 热99久久精品 | 2023国产精品自产拍在线观看 | 欧美人体xx| 国产精品 国产精品 | 69av免费视频 | 91黄视频在线观看 | 在线观看蜜桃视频 | 国产精品免费人成网站 | 色先锋av资源中文字幕 | 久久黄色片 | 免费av观看网站 | 婷婷电影在线观看 | 狠日日| 天堂久久电影网 | 亚洲国产日韩一区 | 97超碰人人模人人人爽人人爱 | 日韩精品免费一区二区在线观看 | 亚洲涩综合 | 色丁香色婷婷 | 日韩精品亚洲专区在线观看 | 五月激情姐姐 | 激情五月开心 | 在线欧美国产 | 波多野结衣一区三区 | 美女啪啪图片 | 午夜久久福利 | av免费电影网站 | 日韩专区视频 | av免费网站在线观看 | 午夜18视频在线观看 | 中文字幕人成不卡一区 | 日韩午夜精品 | 999毛片| 久久理伦片 | 亚洲激情视频在线观看 | 欧美日韩不卡一区二区 | 国产精品成人免费精品自在线观看 | 成人一区在线观看 | 岛国精品一区二区 | 亚州国产精品视频 | 日韩精品一区二区在线观看视频 | 亚洲特级毛片 | 免费视频你懂的 | 国产中文视 | www.久久视频| 免费久久99精品国产婷婷六月 | 精品999在线观看 | 五月天婷亚洲天综合网精品偷 | 欧美日韩久久一区 | 国产一区在线免费观看 | 亚洲欧美日韩中文在线 | 亚洲va欧美 | 99久久精品国产网站 | 亚洲视频网站在线观看 | 天天操天天能 | 国产在线更新 | 亚洲激情视频在线观看 | 国产麻豆传媒 | 国产成人区 | 日韩欧三级 | 精品国产成人av | 狠狠操操操 | 欧美va在线观看 | 欧美国产日韩一区二区三区 | 国产精品一区二区在线看 | 激情丁香月| 国产精品一区二区三区在线免费观看 | 国内外成人免费在线视频 | 伊人国产视频 | 久久久久久久久久久久99 | 超碰在线国产 | 亚洲一区二区91 | 中文字幕在线观看91 | 久草在线费播放视频 | 国产一级视频 | 国产一级片直播 | 国产一区二区精品久久 | 日韩城人在线 | 欧美一级免费高清 | 一本一本久久a久久精品综合 | 国产精品美女久久久久久2018 | 久久精品直播 | 人人看黄色 | 园产精品久久久久久久7电影 | 黄色录像av | 久久久99精品免费观看app | 欧美日韩视频免费看 | 天天爱天天操天天射 | 99久久日韩精品视频免费在线观看 | 狠狠五月婷婷 | 欧美日韩一区二区视频在线观看 | 天天干,天天干 | 在线观看av不卡 | 欧美精品一区二区三区四区在线 | 成人午夜精品福利免费 | 中文字幕资源网在线观看 | 国产精品久久久久久久久久直播 | 欧美 日韩精品 | 久久av一区二区三区亚洲 | 久久久久亚洲精品国产 | 日韩欧美电影在线 | 亚洲日本va午夜在线影院 | 天天色中文| 久久久久免费精品国产 | 蜜桃麻豆www久久囤产精品 | 久久9精品| 国产成人精品亚洲日本在线观看 | 亚洲国产精品影院 | 最近中文字幕高清字幕在线视频 | 国产成人99av超碰超爽 | 国产成人精品亚洲精品 | 五月天视频网站 | 国产丝袜 | 亚洲国产资源 | 亚洲乱码国产乱码精品天美传媒 | 视频精品一区二区三区 | 亚洲午夜久久久久久久久电影网 | 精品一区电影 | 日韩mv欧美mv国产精品 | 黄色a一级视频 | 激情五月播播久久久精品 | 国产专区在线视频 | 丁香在线观看完整电影视频 | 精品久久久久久亚洲综合网 | 国内精品久久久久久久久久久 | 日本精品久久久久中文字幕5 | 天天干天天做天天爱 | 91九色视频在线播放 | 天天做天天爱天天综合网 | 婷五月天激情 | 亚洲国产精品成人女人久久 | 人人澡人人澡人人 | 天天干天天摸天天操 | 日韩视频一区二区三区 | 午夜精品久久久久久久爽 | 久久综合九色综合久99 | 日韩国产在线观看 | 亚洲天堂网站视频 | 久人人| 国产在线日韩 | 四虎国产精品免费观看视频优播 | 天堂av网在线 | 亚洲精品中文在线观看 | 久久久国内精品 | 免费一级片久久 | 国产成人亚洲精品自产在线 | 精品亚洲va在线va天堂资源站 | 国产又粗又猛又爽又黄的视频免费 | 国产黑丝袜在线 | 免费观看一级一片 | a级片韩国 | 中文字幕免费高清av | 97超碰总站| 欧美精品做受xxx性少妇 | 国产精品视频全国免费观看 | 亚洲精品理论 | 99这里只有精品视频 | 国产成人一区二区三区在线观看 | 亚洲aⅴ免费在线观看 | 亚洲免费婷婷 | 夜夜躁日日躁 | 免费在线成人 | 精品一区二区电影 | 国产亚洲视频在线观看 | a视频在线观看 | 天堂av最新网址 |