关于golang:Go语言中new和make你使用哪个来分配内存

10次阅读

共计 5533 个字符,预计需要花费 14 分钟才能阅读完成。

原文链接:Go 语言中 new 和 make 你应用哪个来分配内存?

前言

哈喽,大家好,我是拖更良久的鸽子 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是一个分配内存的内置函数,第一个参数是类型,而不是值,返回的值是指向该类型新调配的零值的指针。
咱们平时在应用指针的时候是须要分配内存空间的,未分配内存空间的指针间接应用会使程序解体,比方这样:

var a *int64
*a = 10

咱们申明了一个指针变量,间接就去应用它,就会应用程序触发 panic,因为当初这个指针变量a 在内存中没有块地址属于它,就无奈间接应用该指针变量,所以 new 函数的作用就呈现了,通过 new 来调配一下内存,就没有问题了:

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 初始化的问题上,先看一个例子:

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 学习交换群,欢送各位大佬们踊跃入群,咱们一起学习交换。入群形式:关注公众号获取。更多学习材料请到公众号支付。

举荐往期文章:

  • Go 看源码必会常识之 unsafe 包
  • 源码分析 panic 与 recover,看不懂你打我好了!
  • 空构造体引发的大型打脸现场
  • Leaf—Segment 分布式 ID 生成零碎(Golang 实现版本)
  • 面试官:两个 nil 比拟后果是什么?
  • 面试官:你能用 Go 写段代码判断以后零碎的存储形式吗?
  • 如何平滑切换线上 Elasticsearch 索引
正文完
 0