golang快速入門[4]-go語言如何編譯爲機器碼express
在本節咱們將介紹go語言中重要的數據類型——數組
數組是一個重要的數據類型,一般會與go語言另外一個重要的結構:切片做對比。
go語言中數組與其餘語言有在顯著的不一樣,包括其不可以進行添加,以及值拷貝的特性。在這一小節中,將會詳細介紹。
//聲明三種方式 var arr [3]int var arr2 = [4]int{1,2,3,4} arr4 :=[...]int{2,3,4}
fmt.Printf("類型arr3: %T,類型arr4: %T\n",arr3,arr4)
len(arr3) arr3[2]
數組在編譯時的數據類型爲TARRAY
,經過NewArray
函數進行建立,AST節點的Op操做:OARRAYLIT
// NewArray returns a new fixed-length array Type. func NewArray(elem *Type, bound int64) *Type { if bound < 0 { Fatalf("NewArray: invalid bound %v", bound) } t := New(TARRAY) t.Extra = &Array{Elem: elem, Bound: bound} t.SetNotInHeap(elem.NotInHeap()) return t }
內部的Array結構存儲了數組中的類型以及數組的大小
// Array contains Type fields specific to array types. type Array struct { Elem *Type // element type Bound int64 // number of elements; <0 if unknown yet }
數組的聲明中,存在一個語法糖。[…]int{2,3,4}
。 其實質與通常的數組聲明相似的。
對於字面量的初始化方式,在編譯時,經過typecheckcomplit
函數循環字面量分別進行賦值。
func typecheckcomplit(n *Node) (res *Node) { nl := n.List.Slice() for i2, l := range nl { i++ if i > length { length = i if checkBounds && length > t.NumElem() { setlineno(l) yyerror("array index %d out of bounds [0:%d]", length-1, t.NumElem()) checkBounds = false } } } if t.IsDDDArray() { t.SetNumElem(length) } } }
抽象的表達就是:
a:=[3]int{2,3,4} 變爲 var arr [3]int a[0] = 2 a[1] = 3 a[2] = 4
若是t.IsDDDArray
判斷到是語法糖的形式進行的數組初始化,那麼會將其長度設置到數組中t.SetNumElem(length)
.
在編譯期的優化階段,還會進行重要的優化。在函數anylit
中,當數組的長度小於4時,在運行時會在棧中進行初始化initKindDynamic
。當數組的長度大於4,會在靜態區初始化數組initKindStatic
.
func anylit(n *Node, var_ *Node, init *Nodes) { t := n.Type switch n.Op { case OSTRUCTLIT, OARRAYLIT: if !t.IsStruct() && !t.IsArray() { Fatalf("anylit: not struct/array") } if var_.isSimpleName() && n.List.Len() > 4 { ... fixedlit(ctxt, initKindStatic, n, vstat, init) // copy static to var a := nod(OAS, var_, vstat) a = typecheck(a, ctxStmt) a = walkexpr(a, init) init.Append(a) // add expressions to automatic fixedlit(inInitFunction, initKindDynamic, n, var_, init) break } }
他們都是經過fixedlit
函數實現的。
func fixedlit(ctxt initContext, kind initKind, n *Node, var_ *Node, init *Nodes) { for _, r := range n.List.Slice() { // build list of assignments: var[index] = expr setlineno(a) a = nod(OAS, a, value) a = typecheck(a, ctxStmt) switch n.Op { ... switch kind { case initKindStatic: genAsStatic(a) case initKindDynamic, initKindLocalCode: a = orderStmtInPlace(a, map[string][]*Node{}) a = walkstmt(a) init.Append(a) default: Fatalf("fixedlit: bad kind %d", kind) } } }
var a [3]int b := a[1]
數組訪問越界是很是嚴重的錯誤,Go 語言中對越界的判斷是能夠在編譯期間由靜態類型檢查完成的,typecheck1
函數會對訪問數組的索引進行驗證:
func typecheck1(n *Node, top int) (res *Node) { switch n.Op { case OINDEX: ok |= ctxExpr l := n.Left // array r := n.Right // index switch n.Left.Type.Etype { case TSTRING, TARRAY, TSLICE: ... if n.Right.Type != nil && !n.Right.Type.IsInteger() { yyerror("non-integer array index %v", n.Right) break } if !n.Bounded() && Isconst(n.Right, CTINT) { x := n.Right.Int64() if x < 0 { yyerror("invalid array index %v (index must be non-negative)", n.Right) } else if n.Left.Type.IsArray() && x >= n.Left.Type.NumElem() { yyerror("invalid array index %v (out of bounds for %d-element array)", n.Right, n.Left.Type.NumElem()) } } } ... } }
訪問數組的索引是非整數時會直接報錯 —— non-integer array index %v;
訪問數組的索引是負數時會直接報錯 —— "invalid array index %v (index must be non-negative)";
訪問數組的索引越界時會直接報錯 —— "invalid array index %v (out of bounds for %d-element array)";
數組和字符串的一些簡單越界錯誤都會在編譯期間發現,好比咱們直接使用整數或者常量訪問數組,可是若是使用變量去訪問數組或者字符串時,編譯器就沒法發現對應的錯誤了,這時就須要在運行時去判斷錯誤。
i:= 3 m:= a[i]
Go 語言運行時在發現數組、切片和字符串的越界操做會由運行時的 panicIndex 和 runtime.goPanicIndex 函數觸發程序的運行時錯誤並致使崩潰退出:
TEXT runtime·panicIndex(SB),NOSPLIT,$0-8 MOVL AX, x+0(FP) MOVL CX, y+4(FP) JMP runtime·goPanicIndex(SB) func goPanicIndex(x int, y int) { panicCheck1(getcallerpc(), "index out of range") panic(boundsError{x: int64(x), signed: true, y: y, code: boundsIndex}) }
最後要提到的是,即使數組的索引是變量。在某些時候仍然可以在編譯時經過優化檢測出越界並在運行時報錯。
例如對於一個簡單的代碼
a := [3]int{1,2,3} b := 8 _ = a[b]
咱們能夠經過以下命令生成ssa.html。顯示整個編譯時的執行過程。
GOSSAFUNC=main GOOS=linux GOARCH=amd64 go tool compile close.go
start階段爲最初生成ssa的階段,
start b1:- v1 (?) = InitMem <mem> v2 (?) = SP <uintptr> v3 (?) = SB <uintptr> v4 (15) = VarDef <mem> {arr} v1 v5 (15) = LocalAddr <*[3]int> {arr} v2 v4 v6 (15) = Zero <mem> {[3]int} [24] v5 v4 v7 (?) = Const64 <int> [1] v8 (15) = LocalAddr <*[3]int> {arr} v2 v6 v9 (?) = Const64 <int> [0] v10 (?) = Const64 <int> [3] v11 (15) = PtrIndex <*int> v8 v9 v12 (15) = Store <mem> {int} v11 v7 v6 v13 (?) = Const64 <int> [2] v14 (15) = LocalAddr <*[3]int> {arr} v2 v12 v15 (15) = PtrIndex <*int> v14 v7 v16 (15) = Store <mem> {int} v15 v13 v12 v17 (15) = LocalAddr <*[3]int> {arr} v2 v16 v18 (15) = PtrIndex <*int> v17 v13 v19 (15) = Store <mem> {int} v18 v10 v16 v20 (?) = Const64 <int> [4] (i[int]) v21 (17) = LocalAddr <*[3]int> {arr} v2 v19 v22 (17) = IsInBounds <bool> v20 v10 If v22 → b2 b3 (likely) (17) b2: ← b1- v25 (17) = PtrIndex <*int> v21 v20 v26 (17) = Copy <mem> v19 v27 (17) = Load <int> v25 v26 (elem[int]) Ret v26 (19) b3: ← b1- v23 (17) = Copy <mem> v19 v24 (17) = PanicBounds <mem> [0] v20 v10 v23 Exit v24 (17)
經過函數IsInBounds判斷數組長度與索引大小進行對比。v22 (17) = IsInBounds v20 v10
,若是失敗即執行v24 (17) = PanicBounds [0] v20 v10 v23
在genssa
生成彙編代碼的階段,咱們可以看到直接被優化爲了00008 (17) CALL runtime.panicIndex(SB)
即在運行時直接會觸發Panic
genssa # main.go 00000 (14) TEXT "".main(SB), ABIInternal 00001 (14) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 00002 (14) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 00003 (14) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) v3 00004 (+17) PCDATA $0, $0 v3 00005 (+17) PCDATA $1, $0 v3 00006 (+17) MOVL $4, AX v19 00007 (17) MOVL $3, CX v24 00008 (17) CALL runtime.panicIndex(SB) 00009 (17) XCHGL AX, AX 00010 (?) END
不管是賦值的b
仍是函數調用中的形參c
,都是值拷貝的
a:= [3]int{1,2,3} b = a func Change(c [3]int){ ... }
咱們能夠經過簡單的打印地址來驗證:
package main import "fmt" func main() { a := [5]int{1,2,3,4,5} fmt.Printf("a:%p\n",&a) b:=a CopyArray(a) fmt.Printf("b:%p\n",&b) } // func CopyArray( c [5]int){ fmt.Printf("c:%p\n",&c) }
輸出爲:
a:0xc00001a150 c:0xc00001a1b0 b:0xc00001a180
說明每個數組在內存的位置都是不相同的,驗證其是值拷貝
數組是go語言中的特殊類型,其與其餘語言不太同樣。他不能夠添加,可是能夠獲取值,獲取長度。
同時,數組的拷貝都是值拷貝,所以不要儘可能不要進行大數組的拷貝。
常量的下標以及某一些變量的下標的訪問越界問題能夠在編譯時檢測到,可是變量的下標的數組越界問題只會在運行時報錯。
數組的聲明中,存在一個語法糖。[…]int{2,3,4}
,可是本質本沒有什麼差異
在編譯期的優化階段,還會進行重要的優化。當數組的長度小於4時,在運行時會在棧中進行初始化。當數組的長度大於4,會在靜態區初始化數組
其實咱們在go語言中對於數組用得較少,而是更多的使用切片。這是下一節的內容。see you~