一文告诉你大神是如何学习Go语言之make和new

 更新时间:2023年02月26日 15:47:48   作者:Draveness  
当我们想要在 Go 语言中初始化一个结构时,其实会使用到两个完全不同的关键字,也就是 make 和 new,同时出现两个用于『初始化』的关键字对于初学者来说可能会感到非常困惑,不过它们两者有着却完全不同的作用,本文就和大家详细讲讲

当我们想要在 Go 语言中初始化一个结构时,其实会使用到两个完全不同的关键字,也就是 make 和 new,同时出现两个用于『初始化』的关键字对于初学者来说可能会感到非常困惑,不过它们两者有着却完全不同的作用。

在 Go 语言中,make 关键字的主要作用是初始化内置的数据结构,也就是我们在前面提到的 数组和切片、哈希表 和 Channel,而当我们想要获取指向某个类型的指针时其实可以使用 new 关键字,只是知道如何使用 new 的人真的比较少,我们在这一节中就会介绍 make 和 new 它们的区别以及实现原理。

概述

虽然 make 和 new  都是能够用于初始化数据结构,但是它们两者能够初始化的结构类型却有着较大的不同;make 在 Go 语言中只能用于初始化语言中的基本类型:

slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)

这些基本类型都是语言为我们提供的,我们在前面的章节中其实已经介绍过了它们初始化的过程以及原理,但是在这里还是需要提醒各位读者注意的是,这三者返回了不同类型的数据结构:

  • slice 是一个包含 datacap 和 len 的结构体
  • hash 是一个指向 hmap 结构体的指针
  • ch 是一个指向 hchan 结构体的指针

而另一个用于初始化数据结构的关键字 new 的作用其实就非常简单了,它只是接收一个类型作为参数然后返回一个指向这个类型的指针:

i := new(int)

var v int
i := &v

上述代码片段中的两种不同初始化方法其实是等价的,它们都会创建一个指向 int 零值的指针。

到了这里我们对 Go 语言中这两种不同关键字的使用也有了一定的了解:make 用于创建切片、哈希表和管道等内置数据结构,new 用于分配并创建一个指向对应类型的指针。

实现原理

接下来我们将分别介绍 make 和 new 在初始化不同数据结构时的具体过程,我们会从编译期间和运行时两个不同的阶段理解这两个关键字的原理,不过由于前面已经详细地介绍过 make 的实现原理,所以我们会将重点放在 new 上从 Go 语言的源代码层面分析它的实现。

make

在前面的章节中我们其实已经谈到过 make 在创建 数组和切片、哈希表 和 Channel 的具体过程,所以在这一小节中,我们也只是会简单提及 make 相关的数据结构初始化原理。

在编译期间的 类型检查 阶段,Go 语言其实就将代表 make 关键字的 OMAKE 节点根据参数类型的不同转换成了 OMAKESLICEOMAKEMAP 和 OMAKECHAN 三种不同类型的节点,这些节点最终也会调用不同的运行时函数来初始化数据结构。

new

内置函数 new 会在编译期间的 SSA 代码生成 阶段经过 callnew 函数的处理,如果请求创建的类型大小时 0,那么就会返回一个表示空指针的 zerobase 变量,在遇到其他情况时会将关键字转换成 newobject

func callnew(t *types.Type) *Node {
    if t.NotInHeap() {
        yyerror("%v is go:notinheap; heap allocation disallowed", t)
    }
    dowidth(t)

    if t.Size() == 0 {
        z := newname(Runtimepkg.Lookup("zerobase"))
        z.SetClass(PEXTERN)
        z.Type = t
        return typecheck(nod(OADDR, z, nil), ctxExpr)
    }

    fn := syslook("newobject")
    fn = substArgTypes(fn, t)
    v := mkcall1(fn, types.NewPtr(t), nil, typename(t))
    v.SetNonNil(true)
    return v
}

需要提到的是,哪怕当前变量是使用 var 进行初始化,在这一阶段可能会被转换成 newobject 的函数调用并在堆上申请内存:

func walkstmt(n *Node) *Node {
    switch n.Op {
    case ODCL:
        v := n.Left
        if v.Class() == PAUTOHEAP {
            if prealloc[v] == nil {
                prealloc[v] = callnew(v.Type)
            }
            nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v])
            nn.SetColas(true)
            nn = typecheck(nn, ctxStmt)
            return walkstmt(nn)
        }
    case ONEW:
        if n.Esc == EscNone {
            r := temp(n.Type.Elem())
            r = nod(OAS, r, nil)
            r = typecheck(r, ctxStmt)
            init.Append(r)
            r = nod(OADDR, r.Left, nil)
            r = typecheck(r, ctxExpr)
            n = r
        } else {
            n = callnew(n.Type.Elem())
        }
    }
}

当然这也不是绝对的,如果当前声明的变量或者参数不需要在当前作用域外『生存』,那么其实就不会被初始化在堆上,而是会初始化在当前函数的栈中并随着 函数调用 的结束而被销毁。

newobject 函数的工作就是获取传入类型的大小并调用 mallocgc 在堆上申请一片大小合适的内存空间并返回指向这片内存空间的指针:

func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

mallocgc 函数的实现大概有 200 多行代码,在这一节中就不展开详细分析了,我们会在后面的章节中详细介绍 Go 语言的内存管理机制。

总结

到了最后,简单总结一下 Go 语言中 make 和 new 关键字的实现原理,make 关键字的主要作用是创建切片、哈希表和 Channel 等内置的数据结构,而 new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针。

Reference

  • Allocation with new
  • Allocation with make
  • Make and new

到此这篇关于一文告诉你大神是如何学习Go语言之make和new的文章就介绍到这了,更多相关Go语言make new内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go中的fuzz模糊测试使用实战详解

    Go中的fuzz模糊测试使用实战详解

    这篇文章主要为大家介绍了Go中的fuzz模糊测试使用实战详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • golang版本升级的简单实现步骤

    golang版本升级的简单实现步骤

    个人感觉Go在众多高级语言中,是在各方面都比较高效的,下面这篇文章主要给大家介绍了关于golang版本升级的简单实现步骤,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • golang中的select关键字用法总结

    golang中的select关键字用法总结

    这篇文章主要介绍了golang中的select关键字用法总结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Golang配置解析神器go viper使用详解

    Golang配置解析神器go viper使用详解

    viper是一个很完善的Go项目配置解决方案,很多著名的开源项目都在使用,比如Hugo,Docker都使用了该库,使用viper可以让我们专注于自己的项目代码,而不用自己写那些配置解析代码,本文给大家介绍Golang配置解析神器go viper使用,感兴趣的朋友一起看看吧
    2022-05-05
  • go循环依赖的最佳解决方案

    go循环依赖的最佳解决方案

      import cycle not allowed(循环依赖不被允许)相信作为每一个golang语言使用研发,都遇到过这个令人头痛的报错,循环依赖是指两个或多个模块之间互相依赖,形成了一个闭环的情况,本文会结合部分案例对解决方案进行讲解,需要的朋友可以参考下
    2023-10-10
  • go语言beego框架web开发语法笔记示例

    go语言beego框架web开发语法笔记示例

    这篇文章主要为大家介绍了go语言beego框架web开发语法笔记示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • golang复制文件夹移动到另一个文件夹实现方法详解

    golang复制文件夹移动到另一个文件夹实现方法详解

    这篇文章主要为大家介绍了golang复制文件夹并移动到另一个文件夹实现方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • go语言面试如何实现自旋锁?

    go语言面试如何实现自旋锁?

    这篇文章主要为大家介绍了go语言面试中常问的如何实现自旋锁问题实例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Golang TCP粘包拆包问题的解决方法

    Golang TCP粘包拆包问题的解决方法

    这篇文章主要给大家介绍了Golang TCP粘包拆包问题的解决方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Golang具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • Golang 中的可测试示例函数(Example Function)详解

    Golang 中的可测试示例函数(Example Function)详解

    这篇文章详细讲解了 Golang 中的可测试示例函数,示例函数类似于单元测试函数,但没有  *testing 类型的参数,编写示例函数也是很容易的,本文就通过代码示例给大家介绍一下Golang的可测试示例函数,需要的朋友可以参考下
    2023-07-07

最新评论