Go語言中new和make你使用哪一個來分配內存?

原文連接:Go語言中new和make你使用哪一個來分配內存?golang

前言

哈嘍,你們好,我是拖更很久的鴿子asong。由於5.1去找女友,因此一直沒有時間寫文章啦,想着回來就抓緊學習,無奈,依然沉浸在5.1的甜蜜生活中,一拖再拖,就到如今啦。果真女人影響了我拔刀的速度,可是我很喜歡,略略略。面試

好啦,不撒狗糧了,開始進入正題,今天咱們就來探討一下Go語言中的makenew到底怎麼使用?它們又有什麼不一樣?數組

分配內存之new

官方文檔定義:數據結構

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

翻譯出來就是:new是一個分配內存的內置函數,第一個參數是類型,而不是值,返回的值是指向該類型新分配的零值的指針。
咱們日常在使用指針的時候是須要分配內存空間的,未分配內存空間的指針直接使用會使程序崩潰,好比這樣:app

var a *int64
*a = 10

咱們聲明瞭一個指針變量,直接就去使用它,就會使用程序觸發panic,由於如今這個指針變量a在內存中沒有塊地址屬於它,就沒法直接使用該指針變量,因此new函數的做用就出現了,經過new來分配一下內存,就沒有問題了:frontend

var a *int64 = new(int64)
    *a = 10

上面的例子,咱們是針對普通類型int64進行new處理的,若是是複合類型,使用new會是什麼樣呢?來看一個示例:分佈式

func main(){
    // 數組
    array := new([5]int64)
    fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{0, 0, 0, 0, 0}
    (*array)[0] = 1
    fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{1, 0, 0, 0, 0}
    
    // 切片
    slice := new([]int64)
    fmt.Printf("slice: %p %#v \n", &slice, slice) // slice: 0xc0000ae028 &[]int64(nil)
    (*slice)[0] = 1
    fmt.Printf("slice: %p %#v \n", &slice, slice) // panic: runtime error: index out of range [0] with length 0

    // map
    map1 := new(map[string]string)
    fmt.Printf("map1: %p %#v \n", &map1, map1) // map1: 0xc00000e038 &map[string]string(nil)
    (*map1)["key"] = "value"
    fmt.Printf("map1: %p %#v \n", &map1, map1) // panic: assignment to entry in nil map

    // channel
    channel := new(chan string)
    fmt.Printf("channel: %p %#v \n", &channel, channel) // channel: 0xc0000ae028 (*chan string)(0xc0000ae030) 
    channel <- "123" // Invalid operation: channel <- "123" (send to non-chan type *chan string) 
}

從運行結果能夠看出,咱們使用new函數分配內存後,只有數組在初始化後能夠直接使用,slicemapchan初始化後仍是不能使用,會觸發panic,這是由於slicemapchan基本數據結構是一個struct,也就是說他裏面的成員變量仍未進行初始化,因此他們初始化要使用make來進行,make會初始化他們的內部結構,咱們下面一節細說。仍是回到struct初始化的問題上,先看一個例子:ide

type test struct {
    A *int64
}

func main(){
    t := new(test)
    *t.A = 10  // panic: runtime error: invalid memory address or nil pointer dereference
             // [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a89fd]
    fmt.Println(t.A)
}

從運行結果得出使用new()函數初始化結構體時,咱們只是初始化了struct這個類型的,而它的成員變量是沒有初始化的,因此初始化結構體不建議使用new函數,使用鍵值對進行初始化效果更佳。函數

其實 new 函數在平常工程代碼中是比較少見的,由於它是能夠被代替,使用T{}方式更加便捷方便。學習

初始化內置結構之make

在上一節咱們說到了,make函數是專門支持 slicemapchannel 三種數據類型的內存建立,其官方定義以下:

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//    Slice: The size specifies the length. The capacity of the slice is
//    equal to its length. A second integer argument may be provided to
//    specify a different capacity; it must be no smaller than the
//    length. For example, make([]int, 0, 10) allocates an underlying array
//    of size 10 and returns a slice of length 0 and capacity 10 that is
//    backed by this underlying array.
//    Map: An empty map is allocated with enough space to hold the
//    specified number of elements. The size may be omitted, in which case
//    a small starting size is allocated.
//    Channel: The channel's buffer is initialized with the specified
//    buffer capacity. If zero, or the size is omitted, the channel is
//    unbuffered.
func make(t Type, size ...IntegerType) Type

大概翻譯最上面一段:make內置函數分配並初始化一個slicemapchan類型的對象。像new函數同樣,第一個參數是類型,而不是值。與new不一樣,make的返回類型與其參數的類型相同,而不是指向它的指針。結果的取決於傳入的類型。

使用make初始化傳入的類型也是不一樣的,具體能夠這樣區分:

Func             Type T     res
make(T, n)       slice      slice of type T with length n and capacity n
make(T, n, m)    slice      slice of type T with length n and capacity m

make(T)          map        map of type T
make(T, n)       map        map of type T with initial space for approximately n elements

make(T)          channel    unbuffered channel of type T
make(T, n)       channel    buffered channel of type T, buffer size n

不一樣的類型初始化可使用不一樣的姿式,主要區別主要是長度(len)和容量(cap)的指定,有的類型是沒有容量這一說法,所以天然也就沒法指定。若是肯定長度和容量大小,能很好節省內存空間。

寫個簡單的示例:

func main(){
    slice := make([]int64, 3, 5)
    fmt.Println(slice) // [0 0 0]
    map1 := make(map[int64]bool, 5)
    fmt.Println(map1) // map[]
    channel := make(chan int, 1)
    fmt.Println(channel) // 0xc000066070
}

這裏有一個須要注意的點,就是slice在進行初始化時,默認會給零值,在開發中要注意這個問題,我就犯過這個錯誤,致使數據不一致。

newmake區別總結

  • new函數主要是爲類型申請一片內存空間,返回執行內存的指針
  • make函數可以分配並初始化類型所需的內存空間和結構,返回複合類型的自己。
  • make函數僅支持 channelmapslice 三種類型,其餘類型不可使用使用make
  • new函數在平常開發中使用是比較少的,能夠被替代。
  • make函數初始化slice會初始化零值,平常開發要注意這個問題。

make函數底層實現

我仍是比較好奇make底層實現是怎樣的,因此執行彙編指令:go tool compile -N -l -S file.go,咱們能夠看到make函數初始化slicemapchan分別調用的是runtime.makesliceruntime.makemap_smallruntime.makechan這三個方法,由於不一樣類型底層數據結構不一樣,因此初始化方式也不一樣,咱們只看一下slice的內部實現就行了,其餘的交給你們本身去看,其實都是大同小異的。

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 {
        // NOTE: Produce a 'len out of range' error instead of a
        // 'cap out of range' error when someone does make([]T, bignumber).
        // 'cap out of range' is true too, but since the cap is only being
        // supplied implicitly, saying len is clearer.
        // See golang.org/issue/4085.
        mem, overflow := math.MulUintptr(et.size, uintptr(len))
        if overflow || mem > maxAlloc || len < 0 {
            panicmakeslicelen()
        }
        panicmakeslicecap()
    }

    return mallocgc(mem, et, true)
}

這個函數功能其實也比較簡單:

  • 檢查切片佔用的內存空間是否溢出。
  • 調用mallocgc在堆上申請一片連續的內存。

檢查內存空間這裏是根據切片容量進行計算的,根據當前切片元素的大小與切片容量的乘積得出當前內存空間的大小,檢查溢出的條件有四個:

  • 內存空間大小溢出了
  • 申請的內存空間大於最大可分配的內存
  • 傳入的len小於0cap的大小隻小於len

mallocgc函數實現比較複雜,我暫時尚未看懂,不過也不是很重要,你們有興趣能夠自行學習。

new函數底層實現

new函數底層主要是調用runtime.newobject

// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

內部實現就是直接調用mallocgc函數去堆上申請內存,返回值是指針類型。

總結

今天這篇文章咱們主要介紹了makenew的使用場景、以及其不一樣之處,其實他們都是用來分配內存的,只不過make函數爲slicemapchan這三種類型服務。平常開發中使用make初始化slice時要注意零值問題,不然又是一個p0事故。

好啦,這篇文章到此結束啦,素質三連(分享、點贊、在看)都是筆者持續創做更多優質內容的動力!我是asong,咱們下期見。

建立了一個Golang學習交流羣,歡迎各位大佬們踊躍入羣,咱們一塊兒學習交流。入羣方式:關注公衆號獲取。更多學習資料請到公衆號領取。

推薦往期文章:

相關文章
相關標籤/搜索