详解Go语言中make和new的区别

不晚
不晚
发布时间:2026-10-15 | 3人浏览过
原创

在 Go 语言开发中,make 和 new 虽然都用于内存相关的初始化操作,但适用场景截然不同。make 仅适用于内置引用类型,负责初始化数据结构本身及关联的容量元数据;而 new 则面向所有类型,执行简单的零值内存分配并返回指针。本文将从语法规范、语义行为及底层实现原理三个维度,详细剖析两者的核心差异。

使用场景与语义差异

make 在 Go 语言中具有唯一性,它只能用于初始化三种内置类型:slice(切片)、map(映射)和 chan(通道)。这些类型属于引用类型,在使用前必须经过初始化过程来建立其底层结构。

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

调用 make 时,它会返回一个初始化好的实例。值得注意的是,这三种返回值的数据形态各不相同:

  • slice 是一个包含 data(数组指针)、cap(容量)和 len(长度)字段的结构体;
  • hash 是一个指向内部 hmap 结构体的指针;
  • ch 是一个指向内部 hchan 结构体的指针。

相比之下,new 的作用非常简单且通用。它接收任意类型作为参数,为该类型在堆上分配空间(若大小大于0),填充零值后,返回一个指向该新分配对象指针。例如:

i := new(int)
// 等价于以下写法
var v int
i := &v

上述代码通过 new(int) 创建了一个指向 int 零值(即 0)的指针。因此,new(T) 的行为等同于 &(*T)(new(T)) 或更直接的地址取用逻辑,它不涉及对数据结构内部的复杂组装。

底层实现原理

理解这两者的区别,需要从编译期和运行时的角度进行分析。Go 编译器在预处理阶段会将这些关键字转换为特定的控制流和操作码。

make 的编译处理

在编译器的类型检查阶段,代表 make 的关键字会被转化为专门的中间表示节点:OMAKESLICE、OMAKEMAP 和 OMAKECHAN。这些节点随后会触发对特定运行时函数的调用,以完成相应类型的底层结构初始化。对于 slice 而言,即便请求初始长度为 0,运行时也会为其分配一个专用的空数组以避免空指针问题,这解释了为何 make([]int, 0) 可以正常调用 append 方法。

new 的编译与堆栈分配

new 函数在 SSA(静态单赋值形式)代码生成阶段,会通过 callnew 进行处理:

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

    // 如果大小为 0,返回 zerobase 变量以避免无效指针
    if t.Size() == 0 {
        z := newname(Runtimepkg.Lookup("zerobase"))
        z.SetClass(PEXTERN)
        z.Type = t
        return typecheck(nod(OADDR, z, nil), ctxExpr)
    }

    // 否则转换为 newobject 调用
    fn := syslook("newobject")
    fn = substArgTypes(fn, t)
    v := mkcall1(fn, types.NewPtr(t), nil, typename(t))
    v.SetNonNil(true)
    return v
}

newobject 的核心逻辑是调用 mallocgc 进行堆内存分配:

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

虽然 new 暗示了堆分配,但在实际编译器优化中,变量是在堆上还是栈上运行取决于逃逸分析。如果变量不需要在当前作用域外生存,编译器会将其分配到栈帧中,随函数结束自动销毁;只有被判定为“逃逸”的变量才会调用 mallocgc 申请堆内存。这一机制显著提升了局部变量的访问效率。

总结

make 专为内置引用类型(slice、map、chan)设计,负责构建完整的内部数据结构及其元数据;new 则用于任何类型的零值内存分配,仅仅返回指向分配的指针。在实际编码中,除非需要显式获取某类型的指针副本,否则应优先遵循 Go 的习惯用语:初始化集合类型使用 make,普通类型直接使用 var 声明配合地址操作即可。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表