NOTE
1.11 array
Fixed-length arrays in Go, value semantics, range iteration, compiler representation, literal initialization, and bounds checking.
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.
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub