Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang new()和make()函数

Golang中的new()和make()函数本质区别

作者:水草

在 Go 语言开发中,new() 和 make() 是两个容易让开发者感到困惑的内建函数,尽管它们都用于内存分配,但其设计目的、适用场景和底层实现存在本质差异,本文将通过类型系统、内存模型和编译器实现三个维度,深入解析这两个函数的本质区别,感兴趣的朋友一起看看吧

在 Go 语言开发中,new() 和 make() 是两个容易让开发者感到困惑的内建函数。尽管它们都用于内存分配,但其设计目的、适用场景和底层实现存在本质差异。本文将通过类型系统、内存模型和编译器实现三个维度,深入解析这两个函数的本质区别。

一、类型系统的哲学分野

1.1 new() 的通用性设计

new(T) 是为所有类型设计的通用内存分配器,其行为模式高度统一:

// 为 int 类型分配零值内存
pInt := new(int)  // *int 类型
// 为自定义结构体分配内存
type MyStruct struct { a int }
pStruct := new(MyStruct) // *MyStruct 类型

其核心特征:

1.2 make() 的特化使命

make() 是 Go 为特定引用类型设计的构造器:

// 创建 slice
s := make([]int, 5, 10) 
// 初始化 map
m := make(map[string]int)
// 建立 channel
ch := make(chan int, 5)

关键限制:

二、内存模型的实现差异

2.1 new() 的底层机制

当编译器遇到 new(T) 时:
1.计算类型大小:size = unsafe.Sizeof(T{})
2.调用 runtime.newobject 分配内存
3.执行内存清零操作(对应零值初始化)
4.返回指向该内存的指针

以下伪代码示意其过程:

func new(T) *T {
    ptr := malloc(sizeof(T))
    *ptr = T{}  // 零值初始化
    return ptr
}

2.2 编译器前端的语法解析

// 原始代码片段
type MyStruct struct { a int }
p := new(MyStruct)
// 转换为中间表示 (IR)
ptr := runtime.newobject(unsafe.Pointer(&MyStruct{}))

编译器会将 new(T) 替换为对 runtime.newobject 的直接调用,传递类型元信息作为参数。

2.3 进入运行时系统的内存分配

runtime.newobject 是 new() 的核心入口,定义于 runtime/malloc.go:

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

关键参数解释

2.4 深入 mallocgc 的内存分配流程

mallocgc 是通用内存分配函数,负责根据对象大小选择不同的分配策略:

微小对象分配(Tiny Allocator)
对于小于 16 字节的对象:

if size <= maxSmallSize {
    if noscan && size < maxTinySize {
        // 使用 per-P 的 tiny allocator
        off := c.tinyoffset
        if off+size <= maxTinySize && c.tiny != 0 {
            x = unsafe.Pointer(c.tiny + off)
            c.tinyoffset = off + size
            return x
        }
        // ...
    }
}

常规对象分配
对于较大的对象,走标准分配路径:

var span *mspan
systemstack(func() {
    span = largeAlloc(size, needzero, noscan)
})
x = unsafe.Pointer(span.base())

2.5 make() 的类型特化处理

编译器将 make 转换为不同的运行时函数调用:

类型内部函数关键参数
sliceruntime.makeslice元素类型、长度、容量
mapruntime.makemap初始 bucket 数量
channelruntime.makechan缓冲区大小

以 slice 为例的底层处理流程:

// 编译器将 make([]int, 5, 10) 转换为
ptr, len, cap := runtime.makeslice(unsafe.Sizeof(int(0)), 5, 10)
return Slice{ptr: ptr, len: 5, cap: 10}

三、零值 vs 就绪状态

3.1 new()零值初始化的实现细节

new() 返回的指针指向的内存会被自动置零:

if needzero {
    memclrNoHeapPointers(x, size)
}

3.2 new() 的零值困境

虽然 new() 能完成基本的内存分配,但对于复杂类型可能产生非预期结果:

// 创建 slice 指针
sp := new([]int)
*sp = append(*sp, 1)  // 合法但非常规用法
(*sp)[0] = 1          // 运行时 panic(索引越界)

此时虽然分配了 slice 头结构(ptr/len/cap),但:

3.3 make() 的初始化保证

make() 确保返回的对象立即可用:

s := make([]int, 5)
s[0] = 1          // 安全操作
ch := make(chan int, 5)
ch <- 1           // 不会阻塞
m := make(map[string]int)
m["key"] = 1      // 不会 panic

初始化过程包括:

四、编译器优化策略

4.1 逃逸分析的差异处理

new() 分配的对象可能被分配到栈上:

func localAlloc() *int {
    return new(int)  // 可能进行栈分配
}

编译器会在编译期间决定对象是否需要分配到堆上:

// 如果发生逃逸,生成 runtime.newobject 调用
if escapeAnalysisResult.escapes {
    call = mkcall("newobject", ...)
} else {
    // 直接在栈上分配空间
}

而 make 创建的对象总是逃逸到堆:

func createSlice() []int {
    return make([]int, 10)  // 必须堆分配
}

4.2 初始化优化

编译器会对 new() 后的立即赋值进行优化:

p := new(int)
*p = 42
// 优化为直接分配已初始化的内存

五、典型平台的汇编输出验证

以 AMD64 平台为例,观察生成的机器码:

//go tool compile -S test.go
MOVQ    $type.MyStruct(SB), AX  ;; 加载类型元数据
CALL    runtime.newobject(SB)   ;; 调用分配函数

六、实践建议与模式选择

6.1 选择决策树

是否创建引用类型?
├─ 是 → 必须使用 make()
└─ 否 → 是否需要指针?
       ├─ 是 → 使用 new()
       └─ 否 → 使用字面量初始化

6.2 性能考量

对于结构体初始化,推荐直接使用值类型:

// 优于 new(MyStruct)
var s MyStruct

当需要明确的指针语义时再使用 new()

6.3 特殊使用模式

组合使用实现延迟初始化:

type LazyContainer struct {
    data *[]string
}
func (lc *LazyContainer) Get() []string {
    if lc.data == nil {
        lc.data = new([]string)
        *lc.data = make([]string, 0, 10)
    }
    return *lc.data
}

七、性能优化启示

1.尽量让小型结构体留在栈上

2.警惕大对象导致的 GC 压力

3.批量初始化替代多次 new()

八、从设计哲学理解差异

Go 语言通过 new 和 make 的分离体现了其类型系统的设计哲学:

这种设计虽然增加了初学者的学习成本,但为大型工程提供了更好的可维护性和运行时安全性。

通过对内存分配机制、编译器优化策略和语言设计哲学的分析,我们可以清晰地认识到:new() 是通用的内存分配原语,而 make() 是针对引用类型的类型感知构造器。理解这一区别有助于开发者写出更符合 Go 语言设计思想的优雅代码。

到此这篇关于Golang中的new()和make()函数的文章就介绍到这了,更多相关Golang new()和make()函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文