golang make new区别

1,023 阅读2分钟

golang中make单指创建初始化 chan、slice、map 这3种类型变量,而new是创建对应类型的指针,new入参是Type,其会初始化对应内存为零值。

func main() {
   v1 := make([]int64, 0)
   v2 := new([]int64)

   fmt.Printf("%v, %v\n", reflect.TypeOf(v1), unsafe.Sizeof(v1))
   fmt.Printf("%v, %v\n", reflect.TypeOf(v2), unsafe.Sizeof(v2))
}

// 打印结果
[]int64, 24 // 24是因为make(slice,n)由于返回的是结构体
*[]int64, 8

func main() {
    slice := make([]int64, 2)
    pslice := new([]int64)

    slice[0] = 1
    slice[1] = 2
    pslice = &slice

    fmt.Printf("%v %v\n", unsafe.Sizeof(slice), unsafe.Sizeof(pslice))
    fmt.Printf("%v %v\n", slice, *pslice)
}

// 打印结果
24 8
[1 2] [1 2]

对于make和new分配的内存,是在堆还是栈上呢? 在继续分析之前,有必要先看下 内存逃逸和逃逸分析:

内存逃逸简单来讲就是 程序中函数都有自己的局部变量和返回地址空间(栈帧),当某个变量想要在函数结束之后继续使用,需要将其分配到堆上,这种从栈上逃逸到堆上的现象 就是内存逃逸。

逃逸分析就是程序在编译期间通过分析函数中变量,哪些需要在堆上分配哪些在栈上分配。逃逸分析为什么是在编译期间做的呢,因为函数局部变量占用多少内存需要在程序编译期间确定,运行期间无法更改。

go的逃逸分析 会遵循2个原则:

  1. 指向栈对象的指针不能存在堆上。
  2. 指向栈对象的指针生命周期不能超过该对象生命周期,也就是栈对象指针不能在栈对象销毁后继续存活。

可以使用如下命令查看go编辑期间的逃逸分析:go build -gcflags '-m -m -l',比如包含 "xxx escapes to heap" 就说明发生了内存逃逸。逃逸分析减轻了gc压力提高运行效率,对于小对象建议直接使用对象,大对象建议使用指针。

  • 对于make和new分配的内存,go编译器尽量将变量分配在栈上,如果变量未发生逃逸,那么就会在栈上分配,否则分配在堆上。
  • 如果变量占用内存很大超过了栈空间,或者栈空间不足,那么就会分配在堆上。注意,占用内存很大的局部变量,这个阈值 不同版本golang的大小限制不一样。

make(slice,n)如果切片大小不固定,其会在堆上分配:

func generate(n int) {
    nums := make([]int, n) // 不确定大小,堆上分配
    for i := 0; i < n; i++ {
        nums[i] = rand.Int()
    }
}

参考资料:

  1. go语言的内存逃逸分析
  2. 如何确定一个 Go 变量会被分配在哪里?
  3. make 和 new