Go make 和 new 的區別

前言

在 go 中對某種類型進行初始化時會用到 makenew, 由於它們的功能類似,因此初學者可能對它們的感到困惑;本文將由淺入深的介紹其功能和區別golang

結論

長話短說,先放上結論:markdown

方法 做用 做用對象 返回值
new 分配內存 值類型和用戶定義的類型 初始化爲零值,返回指針
make 分配內存 內置引用類型(map, slice, channel) 初始化爲零值,返回引用類型自己

以上爲 makenew 的區別,若是有人追問能說的更詳細點嗎?數據結構

哦豁,這就很尷尬了app

正文

短話長說,咱們看看 makenew 究竟作了什麼函數

new

先說要點,new 用來分配內存,並初始化零值,返回零值指針oop

在編譯過程當中,使用 new 大體會產生 2 種狀況:源碼分析

  1. 若該對象申請的空間爲 0,則返回表示空指針的 zerobase 變量,這類對象好比:slice, map, channel 以及一些結構體等。ui

    // path: src/runtime/malloc.go
    
    // base address for all 0-byte allocations
    var zerobase uintptr
    複製代碼
  2. 其餘狀況則會使用 runtime.newobject 函數:spa

    // path: src/runtime/malloc.go
    func newobject(typ *_type) unsafe.Pointer {
    	return mallocgc(typ.size, typ, true)
    }
    複製代碼

這一塊的內容也比較簡單, runtime.newoject 調用了 runtime.mallocgc 函數去開闢一段內存空間,而後返回那塊空間的地址指針

這裏能夠作個簡單的例子去驗證一下:

func main() {
   a := new(map[int]int)
    fmt.Println(*a)  // nil, 參考狀況 1

   b := new(int)
   fmt.Println(*b)    // 0, 參考狀況 2
}
複製代碼

說到底 new 實現什麼功能呢,能夠這樣去理解

// a := new(int); 其餘類型以此類比
var a int
return &a
複製代碼

make

make是用來初始化 map, slice, channel 這幾種特定類型的

在編譯過程當中,用 make 去初始化不一樣的類型會調用不一樣的底層函數:

  1. 初始化 map, 調用 runtime.makemap
  2. 初始化 slice, 調用 runtime.makeslice
  3. 初始化 channel,調用 runtime.makechan

接下來咱們看這些函數的源碼部分,探究它們與 new 的不一樣,若是瞭解這幾種類型的源碼,很容易理解下面的代碼;若是不瞭解這塊內容的同窗能夠跟着註釋走,瞭解流程就能夠了

runtime.makemap:

// path: src/runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {
	...
   // 初始化 Hmap
   if h == nil {
      h = new(hmap)
   }
    
   // 生成 hash 種子
   h.hash0 = fastrand()
    
   // 計算 桶 的數量
   B := uint8(0)
   for overLoadFactor(hint, B) {
      B++
   }
   h.B = B
   if h.B != 0 {
      var nextOverflow *bmap
       
      // 建立 桶
      h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
      ...
   }
   return h
}
複製代碼

這裏爲了方便查看,省去了部分代碼。咱們能夠看到這裏的步驟不少, h = new(hmap)只是其中的一部分

runtime.makeslice:

// path: src/runtime/slice.go
func makeslice(et *_type, len, cap int) unsafe.Pointer {
    // 計算佔用空間和是否溢出
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
    
   // 一些邊界條件處理 
	if overflow || mem > maxAlloc || len < 0 || len > cap {
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
           // panic: len 超出範圍 
			panicmakeslicelen()
		}
        // panic: cap 超出範圍 
		panicmakeslicecap()
	}

	return mallocgc(mem, et, true)
}
複製代碼

這裏其實和 new 底層的 runtime.newobject 很類似了,只是這裏多了一些異常處理

runtime.makechan:

// path: src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
   ...
   var c *hchan
    
   // 針對不一樣狀況下對 channel 實行不一樣的內存分配策略
   switch {
   case mem == 0:
      // 無緩衝區,只給 hchan 分配一段內存
      c = (*hchan)(mallocgc(hchanSize, nil, true))
      c.buf = c.raceaddr()
   case elem.ptrdata == 0:
      // channel 不包含指針,給 hchan 和 緩衝區分配一段連續的內存
      c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
      c.buf = add(unsafe.Pointer(c), hchanSize)
   default:
      // 單獨給 hchan 和 緩衝區分配內存
      c = new(hchan)
      c.buf = mallocgc(mem, elem, true)
   }

   // 初始化 hchan 的內部字段 
   c.elemsize = uint16(elem.size)
   c.elemtype = elem
   c.dataqsiz = uint(size)
   ...
}
複製代碼

這裏省略了部分代碼,包括一些異常處理

總之,make 相對於 new 來講,作的事情更多,new 只是開闢了內存空間, make 爲更加複雜的數據結構開闢內存空間並對一些字段進行初始化

注意:有心細的同窗能夠發現,runtime,makemap, runtime,makeslice, runtime.makechan返回的是指針類型,但並不意味着用 make 初始化後,返回的是指針類型,這裏上面列出來的是比較核心部分的源碼,並非全部的源碼

想了解 make 的更多內容,你們能夠嘗試看一下 mapslicechannel 的源碼

靈魂拷問

這裏有 2 個問題能夠幫你們更好的去理解:

  1. 能夠用 new 去初始化 map, slice 和 channel 嗎?

    首先咱們回憶一下 new 的功能,簡單理解以下:

    var i int
    return &int
    複製代碼

    若是咱們要去初始化上面幾種類型要怎麼去作:

    m := *new(map[int]int)  // 先取值,由於 new 返回的是指針
    	s := *new([]int)
    	ch := *new(chan int)
    複製代碼

    在上述代碼中,使用 new 去初始化這幾個類型,是不會 panic 的

    針對上述代碼的狀況,咱們能夠分類討論:

    1. map, new 沒有對 map 作建立桶等初始操做,因此當咱們添加鍵值對的時候回 panic, 查詢 和 刪除不存在的 key 時不會引起 panic, 由於查詢和刪除都要查找桶和 key的過程,若是沒有對應的桶和key,查詢返回零值,刪除則不做操做
    2. channel,也沒有對 channel 的緩衝區開闢內存空間以及更多的內部初始話操做,所建立的 channel 始終是 nil, 往裏面發送或從裏面接收數據都會引起 panic
    3. **slice, 使用 new 建立的是 nil 切片,它是能夠正常使用的,**由於在切片 append 的過程當中調用 mallocgc 來申請到一塊內存,返回一個新的切片,而後賦值給 nil 切片
  2. 能夠用 make 去初始化其餘類型嗎,如 int, string ?

    不能夠,由於 make 沒有對其餘類型提供相應的底層方法

最後

以上,因爲能力有限,疏忽和不足之處難以免,歡迎讀者指正,以便及時修改。

若本文對你有幫助的話,歡迎 點贊👍 和 轉發,感謝支持!

參考資料

相關文章
相關標籤/搜索