5.5 make and new
...大约 2 分钟
make
和new
都用于初始化变量:
make
的作用是初始化内置的数据结构- 切片
- 哈希表
- Channel
new
的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针
5.5.1 make
编译期会将make
转换成OMAKE
节点,并根据参数不同,转换成:
OMAKESLICE
:初始化sliceOMAKEMAP
:初始化哈希表OMAKECHAN
:初始化channel
5.5.2 new
编译期会在中间代码生成阶段使用两个函数处理:
cmd/compile/internal/gc.callnew
会将关键字转换成ONEWOBJ
类型的节点cmd/compile/internal/gc.state.expr
会根据申请空间的大小分两种情况处理:- 如果申请的空间为 0,就会返回一个表示空指针的
zerobase
变量 - 在遇到其他情况时会将关键字转换成
runtime.newobject
函数
- 如果申请的空间为 0,就会返回一个表示空指针的
func callnew(t *types.Type) *Node {
...
n := nod(ONEWOBJ, typename(t), nil)
...
return n
}
func (s *state) expr(n *Node) *ssa.Value {
switch n.Op {
case ONEWOBJ:
if n.Type.Elem().Size() == 0 {
return s.newValue1A(ssa.OpAddr, n.Type, zerobaseSym, s.sb)
}
typ := s.expr(n.Left)
vv := s.rtcall(newobject, true, []*types.Type{n.Type}, typ)
return vv[0]
}
}
需要注意的是,无论是直接使用 new
,还是使用 var
初始化变量,它们在编译器看来都是 ONEW
和 ODCL
节点。
如果变量会逃逸到堆上,这些节点在这一阶段都会被 cmd/compile/internal/gc.walkstmt
转换成通过 runtime.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())
}
}
}
如果通过 var
或者 new
创建的变量不需要在当前作用域外生存,例如不用作为返回值返回给调用方,那么就不需要初始化在堆上。
runtime.newobject
函数会获取传入类型占用空间的大小,调用 runtime.mallocgc
在堆上申请一片内存空间并返回指向这片内存空间的指针:
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
5.5.3 小结
make
和 new
关键字的实现原理:
make
关键字的作用是创建切片、哈希表和 Channel 等内置的数据结构new
的作用是为类型申请一片内存空间,并返回指向这片内存的指针
Reference
Powered by Waline v2.15.2