您现在的位置是:网站首页> 编程资料编程资料

golang切片原理详细解析_Golang_

2023-05-26 396人已围观

简介 golang切片原理详细解析_Golang_

切片的解析

当我们的代码敲下[]时,便会被go编译器解析为抽象语法树上的切片节点, 被初始化为切片表达式SliceType:

// go/src/cmd/compile/internal/syntax/parser.go // TypeSpec = identifier [ TypeParams ] [ "=" ] Type . func (p *parser) typeDecl(group *Group) Decl { ... if p.tok == _Lbrack { // d.Name "[" ... // array/slice type or type parameter list pos := p.pos() p.next() switch p.tok { ... case _Rbrack: // d.Name "[" "]" ... p.next() d.Type = p.sliceType(pos) ... } } ... } func (p *parser) sliceType(pos Pos) Expr { t := new(SliceType) t.pos = pos t.Elem = p.type_() return t } // go/src/cmd/compile/internal/syntax/nodes.go type ( ... // []Elem SliceType struct { Elem Expr expr } ... )

编译时切片定义为Slice结构体,属性只包含同一类型的元素Elem,编译时通过NewSlice()函数进行创建:

// go/src/cmd/compile/internal/types/type.go type Slice struct { Elem *Type // element type } func NewSlice(elem *Type) *Type { if t := elem.cache.slice; t != nil { if t.Elem() != elem { base.Fatalf("elem mismatch") } if elem.HasTParam() != t.HasTParam() || elem.HasShape() != t.HasShape() { base.Fatalf("Incorrect HasTParam/HasShape flag for cached slice type") } return t } t := newType(TSLICE) t.extra = Slice{Elem: elem} elem.cache.slice = t if elem.HasTParam() { t.SetHasTParam(true) } if elem.HasShape() { t.SetHasShape(true) } return t }

切片的初始化

切片有两种初始化方式,一种声明即初始化称为字面量初始化,一种称为make初始化,

例如:

litSlic := []int{1,2,3,4} // 字面量初始化 makeSlic := make([]int,0) // make初始化

字面量初始化

切片字面量的初始化是在生成抽象语法树后进行遍历的walk阶段完成的。通过walkComplit方法,首先会进行类型检查,此时会计算出切片元素的个数length,然后通过slicelit方法完成具体的初始化工作。整个过程会先创建一个数组存储于静态区(static array),并在堆区创建一个新的切片(auto array),然后将静态区的数据复制到堆区(copy the static array to the auto array),对于切片中的元素会按索引位置一个一个的进行赋值。 在程序启动时这一过程会加快切片的初始化。

// go/src/cmd/compile/internal/walk/complit.go // walkCompLit walks a composite literal node: // OARRAYLIT, OSLICELIT, OMAPLIT, OSTRUCTLIT (all CompLitExpr), or OPTRLIT (AddrExpr). func walkCompLit(n ir.Node, init *ir.Nodes) ir.Node { if isStaticCompositeLiteral(n) && !ssagen.TypeOK(n.Type()) { n := n.(*ir.CompLitExpr) // not OPTRLIT // n can be directly represented in the read-only data section. // Make direct reference to the static data. See issue 12841. vstat := readonlystaticname(n.Type()) fixedlit(inInitFunction, initKindStatic, n, vstat, init) return typecheck.Expr(vstat) } var_ := typecheck.Temp(n.Type()) anylit(n, var_, init) return var_ }

类型检查时,计算出切片长度的过程为:

// go/src/cmd/compile/internal/typecheck/expr.go func tcCompLit(n *ir.CompLitExpr) (res ir.Node) { ... t := n.Type() base.AssertfAt(t != nil, n.Pos(), "missing type in composite literal") switch t.Kind() { ... case types.TSLICE: length := typecheckarraylit(t.Elem(), -1, n.List, "slice literal") n.SetOp(ir.OSLICELIT) n.Len = length ... } return n }

切片的具体初始化过程为:

  • 在静态存储区创建一个数组;
  • 将数组赋值给一个常量部分;
  • 创建一个自动指针即切片分配到堆区,并指向数组;
  • 将数组中的数据从静态区拷贝到切片的堆区;
  • 对每一个切片元素按索引位置分别进行赋值;
  • 最后将分配到堆区的切片赋值给定义的变量;

源代码通过注释也写明了整个过程。

// go/src/cmd/compile/internal/walk/complit.go func anylit(n ir.Node, var_ ir.Node, init *ir.Nodes) { t := n.Type() switch n.Op() { ... case ir.OSLICELIT: n := n.(*ir.CompLitExpr) slicelit(inInitFunction, n, var_, init) ... } } func slicelit(ctxt initContext, n *ir.CompLitExpr, var_ ir.Node, init *ir.Nodes) { // make an array type corresponding the number of elements we have t := types.NewArray(n.Type().Elem(), n.Len) types.CalcSize(t) if ctxt == inNonInitFunction { // put everything into static array vstat := staticinit.StaticName(t) fixedlit(ctxt, initKindStatic, n, vstat, init) fixedlit(ctxt, initKindDynamic, n, vstat, init) // copy static to slice var_ = typecheck.AssignExpr(var_) name, offset, ok := staticinit.StaticLoc(var_) if !ok || name.Class != ir.PEXTERN { base.Fatalf("slicelit: %v", var_) } staticdata.InitSlice(name, offset, vstat.Linksym(), t.NumElem()) return } // recipe for var = []t{...} // 1. make a static array // var vstat [...]t // 2. assign (data statements) the constant part // vstat = constpart{} // 3. make an auto pointer to array and allocate heap to it // var vauto *[...]t = new([...]t) // 4. copy the static array to the auto array // *vauto = vstat // 5. for each dynamic part assign to the array // vauto[i] = dynamic part // 6. assign slice of allocated heap to var // var = vauto[:] // // an optimization is done if there is no constant part // 3. var vauto *[...]t = new([...]t) // 5. vauto[i] = dynamic part // 6. var = vauto[:] // if the literal contains constants, // make static initialized array (1),(2) var vstat ir.Node mode := getdyn(n, true) if mode&initConst != 0 && !isSmallSliceLit(n) { if ctxt == inInitFunction { vstat = readonlystaticname(t) } else { vstat = staticinit.StaticName(t) } fixedlit(ctxt, initKindStatic, n, vstat, init) } // make new auto *array (3 declare) vauto := typecheck.Temp(types.NewPtr(t)) // set auto to point at new temp or heap (3 assign) var a ir.Node if x := n.Prealloc; x != nil { // temp allocated during order.go for dddarg if !types.Identical(t, x.Type()) { panic("dotdotdot base type does not match order's assigned type") } a = initStackTemp(init, x, vstat) } else if n.Esc() == ir.EscNone { a = initStackTemp(init, typecheck.Temp(t), vstat) } else { a = ir.NewUnaryExpr(base.Pos, ir.ONEW, ir.TypeNode(t)) } appendWalkStmt(init, ir.NewAssignStmt(base.Pos, vauto, a)) if vstat != nil && n.Prealloc == nil && n.Esc() != ir.EscNone { // If we allocated on the heap with ONEW, copy the static to the // heap (4). We skip this for stack temporaries, because // initStackTemp already handled the copy. a = ir.NewStarExpr(base.Pos, vauto) appendWalkStmt(init, ir.NewAssignStmt(base.Pos, a, vstat)) } // put dynamics into array (5) var index int64 for _, value := range n.List { if value.Op() == ir.OKEY { kv := value.(*ir.KeyExpr) index = typecheck.IndexConst(kv.Key) if index < 0 { base.Fatalf("slicelit: invalid index %v", kv.Key) } value = kv.Value } a := ir.NewIndexExpr(base.Pos, vauto, ir.NewInt(index)) a.SetBounded(true) index++ // TODO need to check bounds? switch value.Op() { case ir.OSLICELIT: break case ir.OARRAYLIT, ir.OSTRUCTLIT: value := value.(*ir.CompLitExpr) k := initKindDynamic if vstat == nil { // Generate both static and dynamic initializations. // See issue #31987. k = initKindLocalCode } fixedlit(ctxt, k, value, a, init) continue } if vstat != nil && ir.IsConstNode(value) { // already set by copy from static value continue } // build list of vauto[c] = expr ir.SetPos(value) as := ir.NewAssignStmt(base.Pos, a, value) appendWalkStmt(init, orderStmtInPlace(typecheck.Stmt(as), map[string][]*ir.Name{})) } // make slice out of heap (6) a = ir.NewAssignStmt(base.Pos, var_, ir.NewSliceExpr(base.Pos, ir.OSLICE, vauto, nil, nil, nil)) appendWalkStmt(init, orderStmtInPlace(typecheck.Stmt(a), map[string][]*ir.Name{})) }

make初始化

当使用make初始化一个切片时,会被编译器解析为一个OMAKESLICE操作:

// go/src/cmd/compile/internal/walk/expr.go func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node { switch n.Op() { ... case ir.OMAKESLICE: n := n.(*ir.MakeExpr) return walkMakeSlice(n, init) ... }

如果make初始化一个较大的切片则会逃逸到堆中,如果分配了一个较小的切片则直接在栈中分配。

  • walkMakeSlice函数中,如果未指定切片的容量Cap,则初始容量等于切片的长度。
  • 如果切片的初始化未发生内存逃逸n.Esc() == ir.EscNone,则会先在内存中创建一个同样容量大小的数组NewArray(), 然后按切片长度将数组中的值arr[:l]赋予切片。
  • 如果发生了内存逃逸,切片会调用运行时函数makeslicemakeslice64在堆中完成对切片的初始化。
// go/src/cmd/compile/internal/walk/builtin.go func walkMakeSlice(n *ir.MakeExpr, init *ir.Nodes) ir.Node { l := n.Len r := n.Cap if r == nil { r = safeExpr(l, init) l = r } ... if n.Esc() == ir.EscNone { if why := escape.HeapAllocReason(n); why != "" { base.Fatalf("%v has EscNone, but %v", n, why) } // var arr [r]T // n = arr[:l] i := typecheck.IndexConst(r) if i < 0 { base.Fatalf("walkExpr: invalid index %v", r) } ... t = types.NewArray(t.Elem(), i) // [r]T var_ := typecheck.Temp(t) appendWalkStmt(init, ir.NewAssignStmt(base.Pos, var_, nil)) // zero temp r := ir.NewSliceExpr(base.Pos, ir.OSLICE, var_, nil, l, nil) // arr[:l] // The conv is necessary in case n.Type is named. return walkExpr(typecheck.Expr(typecheck.Conv(r, n.Type())), init) } // n escapes; set up a call to makeslice. // When len and cap can fit into int, use makeslice instead of // makeslice64, which is faster and shorter on 32 bit platforms. len, cap := l, r fnname := "makeslice64" argtype := types.Types[types.TINT64] // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. // The case of len or cap overflow when converting 
                
                

-六神源码网