網站首頁 編程語言 正文
編譯時數組類型解析
ArrayType
數組是內存中一片連續的區域,在聲明時需要指定長度,數組的聲明有如下三種方式,[...]
的方式在編譯時會自動推斷長度。
var arr1 [3]int var arr2 = [3]int{1,2,3} arr3 := [...]int{1,2,3}
在詞法及語法解析時,上述三種方式聲明的數組會被解析為ArrayType
, 當遇到[...]
的聲明時,其長度會被標記為nil
,將在后續階段進行自動推斷。
// 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
并將其長度標記為-1
,然后通過check.indexedElts(e.ElemList, utyp.elem, utyp.len)
返回數組長度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
在生成中間結果時,types2.Array
最終會通過types.NewArray()
轉換成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 }
編譯時數組字面量初始化
數組類型解析可以得到數組元素的類型Elem
以及數組長度Bound
,而數組字面量的初始化是在編譯時類型檢查階段完成的,通過函數tcComplit -> typecheckarraylit
循環字面量分別進行賦值。
// 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) ... }
編譯時數組索引越界檢查
在對數組進行索引訪問時,如果訪問越界在編譯時就無法通過檢查。
例如:
arr := [...]string{"s1", "s2", "s3"} e3 := arr[3] // invalid array index 3 (out of bounds for 3-element array)
數組在類型檢查階段會對訪問數組的索引進行驗證:
// 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 }
運行時數組內存分配
數組是內存區域一塊連續的存儲空間。在運行時會通過mallocgc
給數組分配具體的存儲空間。newarray
中如果數組元素剛好只有一個,則空間大小為元素類型的大小typ.size
, 如果有多個元素則內存大小為n*typ.size
。但這并不是實際分配的內存大小,實際分配多少內存,取決于mallocgc
,涉及到golang
的內存分配原理。但可以看到如果待分配的對象不超過32kb
,mallocgc
會直接將其分配在緩存空間中,如果大于32kb
則直接從堆區分配內存空間。
// 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 { ... }
總結
數組在編譯階段最終被解析為types.Array
類型,包含元素類型Elem
和數組長度Bound
type Array struct { Elem *Type // element type Bound int64 // number of elements; <0 if unknown yet }
- 如果數組長度未指定,例如使用了語法糖
[...]
,則會在表達式類型檢查時計算出數組長度。 - 數組字面量初始化以及索引越界檢查都是在編譯時類型檢查階段完成的。
- 在運行時通過
newarray()
函數對數組內存進行分配,如果數組大小超過32kb
則會直接分配到堆區內存。
原文鏈接:https://juejin.cn/post/7105761994069770277
相關推薦
- 2022-11-28 詳解Rust中的變量與常量_Rust語言
- 2022-02-17 RuntimeError: CUDA error: device-side assert trigg
- 2022-03-27 Android實現圓形菜單懸浮窗_Android
- 2022-04-18 uniapp中使用拷貝,復制粘貼功能,uniapp,隱藏軟鍵盤
- 2022-12-15 Go字典使用詳解_Golang
- 2022-08-19 WPF使用DrawingContext實現二維繪圖_C#教程
- 2022-09-01 ASP.NET輕量級MVC框架Nancy的基本用法_實用技巧
- 2022-07-13 IO流之字節流與常見編碼
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支