golang數(shù)組內(nèi)存分配原理
編譯時數(shù)組類型解析
ArrayType
數(shù)組是內(nèi)存中一片連續(xù)的區(qū)域,在聲明時需要指定長度,數(shù)組的聲明有如下三種方式,[...]
的方式在編譯時會自動推斷長度。
var arr1 [3]int var arr2 = [3]int{1,2,3} arr3 := [...]int{1,2,3}
在詞法及語法解析時,上述三種方式聲明的數(shù)組會被解析為ArrayType
, 當(dāng)遇到[...]
的聲明時,其長度會被標(biāo)記為nil
,將在后續(xù)階段進行自動推斷。
// go/src/cmd/compile/internal/syntax/parser.go func (p *parser) typeOrNil() Expr { ... pos := p.pos() switch p.tok { ... case _Lbrack: // '[' oexpr ']' ntype // '[' _DotDotDot ']' ntype p.next() if p.got(_Rbrack) { return p.sliceType(pos) } return p.arrayType(pos, nil) ... } // "[" has already been consumed, and pos is its position. // If len != nil it is the already consumed array length. func (p *parser) arrayType(pos Pos, len Expr) Expr { ... if len == nil && !p.got(_DotDotDot) { p.xnest++ len = p.expr() p.xnest-- } ... p.want(_Rbrack) t := new(ArrayType) t.pos = pos t.Len = len t.Elem = p.type_() return t }
// go/src/cmd/compile/internal/syntax/nodes.go type ( ... // [Len]Elem ArrayType struct { Len Expr // nil means Len is ... Elem Expr expr } ... )
types2.Array
在對生成的表達式進行類型檢查時,如果是ArrayType
類型,且其長度Len
為nil
時,會初始化一個types2.Array
并將其長度標(biāo)記為-1
,然后通過check.indexedElts(e.ElemList, utyp.elem, utyp.len)
返回數(shù)組長度n
并賦值給Len
,完成自動推斷。
// go/src/cmd/compile/internal/types2/array.go // An Array represents an array type. type Array struct { len int64 elem Type }
// go/src/cmd/compile/internal/types2/expr.go // exprInternal contains the core of type checking of expressions. // Must only be called by rawExpr. func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKind { ... switch e := e.(type) { ... case *syntax.CompositeLit: var typ, base Type switch { case e.Type != nil: // composite literal type present - use it // [...]T array types may only appear with composite literals. // Check for them here so we don't have to handle ... in general. if atyp, _ := e.Type.(*syntax.ArrayType); atyp != nil && atyp.Len == nil { // We have an "open" [...]T array type. // Create a new ArrayType with unknown length (-1) // and finish setting it up after analyzing the literal. typ = &Array{len: -1, elem: check.varType(atyp.Elem)} base = typ break } typ = check.typ(e.Type) base = typ ... } switch utyp := coreType(base).(type) { ... case *Array: if utyp.elem == nil { check.error(e, "illegal cycle in type declaration") goto Error } n := check.indexedElts(e.ElemList, utyp.elem, utyp.len) // If we have an array of unknown length (usually [...]T arrays, but also // arrays [n]T where n is invalid) set the length now that we know it and // record the type for the array (usually done by check.typ which is not // called for [...]T). We handle [...]T arrays and arrays with invalid // length the same here because it makes sense to "guess" the length for // the latter if we have a composite literal; e.g. for [n]int{1, 2, 3} // where n is invalid for some reason, it seems fair to assume it should // be 3 (see also Checked.arrayLength and issue #27346). if utyp.len < 0 { utyp.len = n // e.Type is missing if we have a composite literal element // that is itself a composite literal with omitted type. In // that case there is nothing to record (there is no type in // the source at that point). if e.Type != nil { check.recordTypeAndValue(e.Type, typexpr, utyp, nil) } } ... } ... }
types.Array
在生成中間結(jié)果時,types2.Array
最終會通過types.NewArray()
轉(zhuǎn)換成types.Array
類型。
// go/src/cmd/compile/internal/noder/types.go // typ0 converts a types2.Type to a types.Type, but doesn't do the caching check // at the top level. func (g *irgen) typ0(typ types2.Type) *types.Type { switch typ := typ.(type) { ... case *types2.Array: return types.NewArray(g.typ1(typ.Elem()), typ.Len()) ... }
// go/src/cmd/compile/internal/types/type.go // Array contains Type fields specific to array types. type Array struct { Elem *Type // element type Bound int64 // number of elements; <0 if unknown yet } // NewArray returns a new fixed-length array Type. func NewArray(elem *Type, bound int64) *Type { if bound < 0 { base.Fatalf("NewArray: invalid bound %v", bound) } t := newType(TARRAY) t.extra = &Array{Elem: elem, Bound: bound} t.SetNotInHeap(elem.NotInHeap()) if elem.HasTParam() { t.SetHasTParam(true) } if elem.HasShape() { t.SetHasShape(true) } return t }
編譯時數(shù)組字面量初始化
數(shù)組類型解析可以得到數(shù)組元素的類型Elem
以及數(shù)組長度Bound
,而數(shù)組字面量的初始化是在編譯時類型檢查階段完成的,通過函數(shù)tcComplit -> typecheckarraylit
循環(huán)字面量分別進行賦值。
// 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.TARRAY: typecheckarraylit(t.Elem(), t.NumElem(), n.List, "array literal") n.SetOp(ir.OARRAYLIT) ... return n }
// go/src/cmd/compile/internal/typecheck/typecheck.go // typecheckarraylit type-checks a sequence of slice/array literal elements. func typecheckarraylit(elemType *types.Type, bound int64, elts []ir.Node, ctx string) int64 { ... for i, elt := range elts { ir.SetPos(elt) r := elts[i] ... r = Expr(r) r = AssignConv(r, elemType, ctx) ... }
編譯時數(shù)組索引越界檢查
在對數(shù)組進行索引訪問時,如果訪問越界在編譯時就無法通過檢查。
例如:
arr := [...]string{"s1", "s2", "s3"} e3 := arr[3] // invalid array index 3 (out of bounds for 3-element array)
數(shù)組在類型檢查階段會對訪問數(shù)組的索引進行驗證:
// go/src/cmd/compile/internal/typecheck/typecheck.go func typecheck1(n ir.Node, top int) ir.Node { ... switch n.Op() { ... case ir.OINDEX: n := n.(*ir.IndexExpr) return tcIndex(n) ... } } // go/src/cmd/compile/internal/typecheck/expr.go func tcIndex(n *ir.IndexExpr) ir.Node { ... l := n.X n.Index = Expr(n.Index) r := n.Index t := l.Type() ... switch t.Kind() { ... case types.TSTRING, types.TARRAY, types.TSLICE: n.Index = indexlit(n.Index) if t.IsString() { n.SetType(types.ByteType) } else { n.SetType(t.Elem()) } why := "string" if t.IsArray() { why = "array" } else if t.IsSlice() { why = "slice" } if n.Index.Type() != nil && !n.Index.Type().IsInteger() { base.Errorf("non-integer %s index %v", why, n.Index) return n } if !n.Bounded() && ir.IsConst(n.Index, constant.Int) { x := n.Index.Val() if constant.Sign(x) < 0 { base.Errorf("invalid %s index %v (index must be non-negative)", why, n.Index) } else if t.IsArray() && constant.Compare(x, token.GEQ, constant.MakeInt64(t.NumElem())) { base.Errorf("invalid array index %v (out of bounds for %d-element array)", n.Index, t.NumElem()) } else if ir.IsConst(n.X, constant.String) && constant.Compare(x, token.GEQ, constant.MakeInt64(int64(len(ir.StringVal(n.X))))) { base.Errorf("invalid string index %v (out of bounds for %d-byte string)", n.Index, len(ir.StringVal(n.X))) } else if ir.ConstOverflow(x, types.Types[types.TINT]) { base.Errorf("invalid %s index %v (index too large)", why, n.Index) } } ... } return n }
運行時數(shù)組內(nèi)存分配
數(shù)組是內(nèi)存區(qū)域一塊連續(xù)的存儲空間。在運行時會通過mallocgc
給數(shù)組分配具體的存儲空間。newarray
中如果數(shù)組元素剛好只有一個,則空間大小為元素類型的大小typ.size
, 如果有多個元素則內(nèi)存大小為n*typ.size
。但這并不是實際分配的內(nèi)存大小,實際分配多少內(nèi)存,取決于mallocgc
,涉及到golang
的內(nèi)存分配原理。但可以看到如果待分配的對象不超過32kb
,mallocgc
會直接將其分配在緩存空間中,如果大于32kb
則直接從堆區(qū)分配內(nèi)存空間。
// go/src/runtime/malloc.go // newarray allocates an array of n elements of type typ. func newarray(typ *_type, n int) unsafe.Pointer { if n == 1 { return mallocgc(typ.size, typ, true) } mem, overflow := math.MulUintptr(typ.size, uintptr(n)) if overflow || mem > maxAlloc || n < 0 { panic(plainError("runtime: allocation size out of range")) } return mallocgc(mem, typ, true) } // Allocate an object of size bytes. // Small objects are allocated from the per-P cache's free lists. // Large objects (> 32 kB) are allocated straight from the heap. func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... }
總結(jié)
數(shù)組在編譯階段最終被解析為types.Array
類型,包含元素類型Elem
和數(shù)組長度Bound
type Array struct { Elem *Type // element type Bound int64 // number of elements; <0 if unknown yet }
- 如果數(shù)組長度未指定,例如使用了語法糖
[...]
,則會在表達式類型檢查時計算出數(shù)組長度。 - 數(shù)組字面量初始化以及索引越界檢查都是在編譯時類型檢查階段完成的。
- 在運行時通過
newarray()
函數(shù)對數(shù)組內(nèi)存進行分配,如果數(shù)組大小超過32kb
則會直接分配到堆區(qū)內(nèi)存。
到此這篇關(guān)于golang數(shù)組內(nèi)存分配原理的文章就介紹到這了,更多相關(guān)golang數(shù)組原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang10進制轉(zhuǎn)16進制的幾種方法代碼示例
這篇文章主要給大家介紹了關(guān)于Golang10進制轉(zhuǎn)16進制的幾種方法,進制轉(zhuǎn)換是Golang的一些基本操作,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用詳解
這篇文章腫,將以一個簡單的文件打開的系統(tǒng)調(diào)用,來分析一下Go調(diào)度器在系統(tǒng)調(diào)用時做了什么。文中的示例代碼講解詳細,需要的可以參考一下2023-04-04解析golang 標(biāo)準(zhǔn)庫template的代碼生成方法
這個項目的自動生成代碼都是基于 golang 的標(biāo)準(zhǔn)庫 template 的,所以這篇文章也算是對使用 template 庫的一次總結(jié),本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2021-11-11Go語言rune與字符串轉(zhuǎn)換的密切關(guān)系解析
這篇文章主要為大家介紹了Go語言rune與字符串轉(zhuǎn)換的密切關(guān)系示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12Golang使用pprof和trace進行診斷和修復(fù)性能問題
在?Go?中,開發(fā)人員可以使用強大的內(nèi)置工具來幫助診斷和修復(fù)性能問題,其中兩個工具是?pprof?和?trace?包,下面就跟隨小編一起來了解下如何使用pprof和trace進行診斷和修復(fù)性能問題吧2024-01-01