区别
介绍
- Go语言中 new 和 make 是两个内置函数,主要用来创建并分配类型的内存。
- new 和 make 主要区别如下:
- make:
- 只能用来分配及初始化类型为 slice、map、chan 的数据。
- 返回引用,即 Type。
- make 分配空间后,会进行初始化。
- new:
- 可以分配任意类型的数据。
- 返回的是指针,即类型 *Type。
- new 分配的空间被清零。
- make:
new
- new 函数只接受一个参数,这个参数是一个类型。
- 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
示例
```go type Student struct { name string age int }
func main() { var s *Student s = new(Student) //分配空间 s.name = “dequan” fmt.Println(s) // &{dequan 0}
var sum *int
sum = new(int) //分配空间
*sum = 98
fmt.Println(*sum) // 98
}
如果我们不使用 new 函数为自定义类型分配空间(将第 8 行注释),就会报错:<br />**panic: **runtime error: invalid memory address or nil pointer dereference
---
<a name="fJwGM"></a>
### make
1. make 也是用于内存分配的。
1. 它只用于 chan、map 以及 slice 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
```go
// 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, so make([]int, 0, 10) allocates a slice of length 0 and
// capacity 10.
// 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
示例
s := make([]uint, 1, 10)
fmt.Println(s) // [0]
原理
接下来我们将分别介绍一下 make 和 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)
}
make
在编译期的类型检查阶段,Go语言其实就将代表 make 关键字的 OMAKE 节点根据参数类型的不同转换成了 OMAKESLICE、OMAKEMAP 和 OMAKECHAN 三种不同类型的节点,这些节点最终也会调用不同的运行时函数来初始化数据结构。