likes
comments
collection
share

Go 语言中 make 和new 的区别

作者站长头像
站长
· 阅读数 18

Go 语言中 make 和new 的区别

声明一个变量

var a int
var b string

这里我们使用 var 关键字,声明两个变量,然后就可以在程序中使用。当我们不指定变量的默认值的时候呢,这些变量的默认值是它所属类型的零值。比如上面的 int 型它的零值为 0,string 的零值为 "",引用类型的零值为 nil。

接下来尝试一下定义引用类型。

package main

import "fmt"

func main() {
    var a *int
    *a = 10
    fmt.Println(*a)
}

执行一下这个程序,它会发生 panic,原因是:

panic: runtime error: invalid memory address or nil pointer dereference

从这我们可以看出,如果是一个引用类型,我们不仅要声明它,还要为它分配内存空间,否则我们赋值的 10 就无处安放。值类型的声明不需要我们分配内存空间,因为已经默认给我们分配好啦。

new 关键字

我们使用 new 关键字来改写一下上面的 demo

package main

import "fmt"

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

我们可以运行成功,并且成功打印出 10。下面贴出 new 内置函数的注释。

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly\
// allocated zero value of that type.
func new(Type) *Type

简单翻译:此内置函数用于分配内存,第一个参数接收一个类型而不是一个值,函数返回一个指向该类型内存地址的指针,同时把分配的内存置为该类型的零值。

内置函数 new 会在编译期的,Go 语言编译器的中间代码具有静态单赋值(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)
}

make 关键字

make 也是用于分配内存,与 new 不同的是,它一般只用于chan,map,slice 的初始化,并且直接返回这三种类型本身。以下是 make 内置函数的注释。

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//  Slice: The size specifies the length. The capacity of the slice is
//  equal to its length. A second integer argument may be provided to
//  specify a different capacity; it must be no smaller than the
//  length. For example, make([]int, 0, 10) allocates an underlying array
//  of size 10 and returns a slice of length 0 and capacity 10 that is
//  backed by this underlying array.
//  Map: An empty map is allocated with enough space to hold the
//  specified number of elements. The size may be omitted, in which case
//  a small starting size is allocated.
//  Channel: The channel's buffer is initialized with the specified
//  buffer capacity. If zero, or the size is omitted, the channel is
//  unbuffered.
func make(t Type, size ...IntegerType) Type

从声明也能看出,返回的还是该类型。

new的语法是:func new(Type) *Type;
make的语法为:func make(t Type, size …IntegerType) Type

两者异同

  • new 和 make 都是用于内存的分配。
  • make 只用于 chan,map,slice 的初始化。
  • new 用于给类型分配内存空间,并且置零。
  • make 返回类型本身,new 返回指向类型的指针。

参考资料