日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解Go語言中make和new的區別_Golang

作者:nil ? 更新時間: 2023-06-18 編程語言

寫在前面

雖然?make?和?new??都是能夠用于初始化數據結構,但是它們兩者能夠初始化的結構類型卻有著較大的不同;make?在 Go 語言中只能用于初始化語言中的3種類型:slice、map、chan

slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)

這些基本類型都是語言為我們提供的,我們在前面的章節中其實已經介紹過了它們初始化的過程以及原理,但是在這里還是需要提醒各位讀者注意的是,這三者返回了不同類型的數據結構:

  • slice?是一個包含?datacap?和?len?的結構體
  • hash?是一個指向?hmap?結構體的指針
  • ch?是一個指向?hchan?結構體的指針

而另一個用于初始化數據結構的關鍵字?new?的作用其實就非常簡單了,它只是接收一個類型作為參數然后返回一個指向這個類型的指針:

i := new(int)

var v int
i := &v

上述代碼片段中的兩種不同初始化方法其實是等價的,它們都會創建一個指向?int?零值的指針。

到了這里我們對 Go 語言中這兩種不同關鍵字的使用也有了一定的了解:make?用于創建切片、哈希表和管道等內置數據結構,new?用于分配并創建一個指向對應類型的指針。

實現原理

接下來我們將分別介紹?make?和?new?在初始化不同數據結構時的具體過程,我們會從編譯期間和運行時兩個不同的階段理解這兩個關鍵字的原理,不過由于前面已經詳細地介紹過?make?的實現原理,所以我們會將重點放在?new?上從 Go 語言的源代碼層面分析它的實現。

make

在前面的章節中我們其實已經談到過?make?在創建 數組和切片、哈希表 和 Channel 的具體過程,所以在這一小節中,我們也只是會簡單提及?make?相關的數據結構初始化原理。

在編譯期間的 類型檢查 階段,Go 語言其實就將代表?make?關鍵字的?OMAKE?節點根據參數類型的不同轉換成了?OMAKESLICEOMAKEMAP?和?OMAKECHAN?三種不同類型的節點,這些節點最終也會調用不同的運行時函數來初始化數據結構。

new

內置函數?new?會在編譯期間的 SSA 代碼生成 階段經過?callnew?函數的處理,如果請求創建的類型大小時 0,那么就會返回一個表示空指針的?zerobase?變量,在遇到其他情況時會將關鍵字轉換成?newobject

func callnew(t *types.Type) *Node {
    if t.NotInHeap() {
        yyerror("%v is go:notinheap; heap allocation disallowed", t)
    }
    dowidth(t)

    if t.Size() == 0 {
        z := newname(Runtimepkg.Lookup("zerobase"))
        z.SetClass(PEXTERN)
        z.Type = t
        return typecheck(nod(OADDR, z, nil), ctxExpr)
    }

    fn := syslook("newobject")
    fn = substArgTypes(fn, t)
    v := mkcall1(fn, types.NewPtr(t), nil, typename(t))
    v.SetNonNil(true)
    return v
}

需要提到的是,哪怕當前變量是使用?var?進行初始化,在這一階段可能會被轉換成?newobject?的函數調用并在堆上申請內存:

func walkstmt(n *Node) *Node {
    switch n.Op {
    case ODCL:
        v := n.Left
        if v.Class() == PAUTOHEAP {
            if prealloc[v] == nil {
                prealloc[v] = callnew(v.Type)
            }
            nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v])
            nn.SetColas(true)
            nn = typecheck(nn, ctxStmt)
            return walkstmt(nn)
        }
    case ONEW:
        if n.Esc == EscNone {
            r := temp(n.Type.Elem())
            r = nod(OAS, r, nil)
            r = typecheck(r, ctxStmt)
            init.Append(r)
            r = nod(OADDR, r.Left, nil)
            r = typecheck(r, ctxExpr)
            n = r
        } else {
            n = callnew(n.Type.Elem())
        }
    }
}

當然這也不是絕對的,如果當前聲明的變量或者參數不需要在當前作用域外『生存』,那么其實就不會被初始化在堆上,而是會初始化在當前函數的棧中并隨著 函數調用 的結束而被銷毀。

newobject?函數的工作就是獲取傳入類型的大小并調用?mallocgc?在堆上申請一片大小合適的內存空間并返回指向這片內存空間的指針:

func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

mallocgc?函數的實現大概有 200 多行代碼,在這一節中就不展開詳細分析了,我們會在后面的章節中詳細介紹 Go 語言的內存管理機制。

總結

到了最后,簡單總結一下 Go 語言中?make?和?new?關鍵字的實現原理,make?關鍵字的主要作用是創建切片、哈希表和 Channel 等內置的數據結構,而?new?的主要作用是為類型申請一片內存空間,并返回指向這片內存的指針。

參考

[1]大神是如何學習 Go 語言之 make 和 new

原文鏈接:https://juejin.cn/post/7198126784763953210

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新