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 没有对其余类型提供相应的底层方法

最后

以上,因为能力有限,疏忽和不足之处难以免,欢迎读者指正,以便及时修改。

若本文对你有帮助的话,欢迎 点赞👍 和 转发,感谢支持!

参考资料