NOTE

1.11 array

Fixed-length arrays in Go, value semantics, range iteration, compiler representation, literal initialization, and bounds checking.

GoCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What It Is

A fixed-length array.

2. Usage

2.1. The length of an array is also part of its type

func TestArray(t *testing.T) {
	arrays := [3]int{1, 2, 3}
	arrays2 := [4]int{1, 2, 3}
	fmt.Println(reflect.DeepEqual(arrays,arrays2))
	//fmt.Println(arrays == arrays2)//Invalid operation: arrays == arrays2 (mismatched types [3]int and [4]int)

}

// output
false

2.2. Arrays are passed by value

A new copy of the array is made during assignment and function argument passing.

func TestArray2(t *testing.T) {
	arrays := [...]int{1, 2, 3}
	testArray(arrays)
	fmt.Println(arrays)

	testArrayAddr(&arrays)
	fmt.Println(arrays)

}

// Modifications here are reflected back to the caller
func testArrayAddr(arrays *[3]int) {
	arrays[0] = 33333

}

// Arrays in Golang are passed by value, so a completely identical copy is made
// Note that the array length is also part of the type; using [...]int as a parameter would report an error
func testArray(arrays [3]int) {
	arrays[0] = 2222
	fmt.Println(arrays)

}


// output
[2222 2 3]
[1 2 3]
[33333 2 3]

2.3. range can be used for iteration

func TestArray3(t *testing.T) {
	a := [...]int{999, 888, 777}

	for i := range a {
		fmt.Printf("a[%d]: %d\n", i, a[i])
	}
	fmt.Println("==========")
	for i, v := range a  {
		fmt.Printf("a[%d]: %d\n", i, v)
	}
	fmt.Println("==========")
	for i := 0; i < len(a ); i++ {
		fmt.Printf("a[%d]: %d\n", i, a [i])
	}
}

// output
a[0]: 999
a[1]: 888
a[2]: 777
==========
a[0]: 999
a[1]: 888
a[2]: 777
==========
a[0]: 999
a[1]: 888
a[2]: 777

3. Implementation

3.1. Data Structure

type Array struct {
	Elem  *Type // type
	Bound int64 // length
}

3.2. Creation

3.2.1. Initialization

  • First method
arr1 := [3]int{1, 2, 3}

This calls cmd/compile/internal/types.NewArray.

func NewArray(elem *Type, bound int64) *Type {
	if bound < 0 {
		Fatalf("NewArray: invalid bound %v", bound)
	}
	t := New(TARRAY)
	// Array contains two fields, Elem and Bound
	t.Extra = &Array{Elem: elem, Bound: bound}
	// Whether the current array should be initialized on the heap or stack is also determined at compile time
	t.SetNotInHeap(elem.NotInHeap())
	return t
}
  • Second method
arr2 := [...]int{1, 2, 3}

This also calls cmd/compile/internal/types.NewArray, with Bound set to -1. Later, cmd/compile/internal/gc.typecheckcomplit is called to set the length.

func typecheckcomplit(n *Node) (res *Node) {
	...

	switch t.Etype {
	case TARRAY, TSLICE:
		var length, i int64
		nl := n.List.Slice()
		for i2, l := range nl {
			i++
			if i > length {
				length = i
			}
		}
        
        // If it is ..., reset the length
		if t.IsDDDArray() {
			t.SetNumElem(length)
		}
	}
}

3.2.2. Where literals are stored

func anylit(n *Node, var_ *Node, init *Nodes) {
	t := n.Type
	switch n.Op {
	case OSTRUCTLIT, OARRAYLIT:
		if n.List.Len() > 4 {
			...
		}

		fixedlit(inInitFunction, initKindLocalCode, n, var_, init)
	...
	}
}

When the number of elements is less than or equal to 4, the array elements are placed directly on the stack.

func fixedlit(ctxt initContext, kind initKind, n *Node, var_ *Node, init *Nodes) {
	var splitnode func(*Node) (a *Node, value *Node)
	...

	for _, r := range n.List.Slice() {
		a, value := splitnode(r)
		a = nod(OAS, a, value)
		a = typecheck(a, ctxStmt)
		switch kind {
		case initKindStatic:
			genAsStatic(a)
		// The following path is equivalent to
		//var arr [3]int
        //arr[0] = 1
        //arr[1] = 2
        //arr[2] = 3
		case initKindLocalCode:
			a = orderStmtInPlace(a, map[string][]*Node{})
			a = walkstmt(a)
			init.Append(a)
		}
	}
}

When the number of elements is greater than 4, the array elements are placed in the static area and retrieved at runtime.

func anylit(n *Node, var_ *Node, init *Nodes) {
	t := n.Type
	switch n.Op {
	case OSTRUCTLIT, OARRAYLIT:
		if n.List.Len() > 4 {
			vstat := staticname(t)
			vstat.Name.SetReadonly(true)

            // Equivalent to
            //var arr [5]int
            //statictmp_0[0] = 1
            //statictmp_0[1] = 2
            //statictmp_0[2] = 3
            //statictmp_0[3] = 4
            //statictmp_0[4] = 5
            //arr = statictmp_0
			fixedlit(inNonInitFunction, initKindStatic, n, vstat, init)

			a := nod(OAS, var_, vstat)
			a = typecheck(a, ctxStmt)
			a = walkexpr(a, init)
			init.Append(a)
			break
		}
		
		...
	}
}

3.3. Access and Assignment

3.3.1. Bounds Checking

When a constant array index is used, execution reaches cmd/compile/internal/gc.typecheck1, where bounds checking is performed at compile time.

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:
			...
			// When the array index is not an integer
			if n.Right.Type != nil && !n.Right.Type.IsInteger() {
				yyerror("non-integer array index %v", n.Right)
				break
			}
			// When the array index is negative
			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)
				}
				// When the array index is out of bounds
				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())
				}
			}
		}
	...
	}
}

If a variable array index is used, execution reaches runtime.goPanicIndex, where bounds checking is performed at runtime.

4. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub