Go make 和 new 的区别

前言

在 go 中对某种类型进行初始化时会用到 makenew, 因为它们的功能相似,所以初学者可能对它们的感到困惑;本文将由浅入深的介绍其功能和区别

结论

长话短说,先放上结论:

方法 作用 作用对象 返回值
new 分配内存 值类型和用户定义的类型 初始化为零值,返回指针
make 分配内存 内置引用类型(map, slice, channel) 初始化为零值,返回引用类型本身

以上为 makenew 的区别,如果有人追问能说的更详细点吗?

哦豁,这就很尴尬了

正文

短话长说,我们看看 makenew 究竟做了什么

new

先说要点,new 用来分配内存,并初始化零值,返回零值指针

在编译过程中,使用 new 大致会产生 2 种情况:

  1. 若该对象申请的空间为 0,则返回表示空指针的 zerobase 变量,这类对象比如:slice, map, channel 以及一些结构体等。

    // path: src/runtime/malloc.go
    
    // base address for all 0-byte allocations
    var zerobase uintptr
    
  2. 其他情况则会使用 runtime.newobject 函数:

    // path: src/runtime/malloc.go
    func newobject(typ *_type) unsafe.Pointer {
     return mallocgc(typ.size, typ, true)
    }
    
   
   这一块的内容也比较简单, `runtime.newoject` 调用了 `runtime.mallocgc` 函数去开辟一段内存空间,然后返回那块空间的地址

这里可以做个简单的例子去验证一下:

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

最后

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

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

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343