Массив НЕ указатель

Массив НЕ указатель (на какую-то область памяти) и внутри него НЕТ никакого указателя.
Это структура данных, у которой есть только:

  • адрес начала
  • длина
  • размер элементов
var values = [...]int{10, 20, 30}
 
var value1 int = 10
var value2 int = 20
var value3 int = 30

Оба объявления “эквиваленты”, просто массив условно объединил эти три переменные в одну область памяти и хранит начало этой области памяти, длина и размер (в байтах, типа) элемента (каждого).

Creating

var arr1 [5]int               // [0 0 0 0 0]
var arr2 [2][5]int            // [[0 0 0 0 0] [0 0 0 0 0]]
arr3 := [...]int{1, 2, 3}     // [1 2 3]
arr4 := [5]int{1, 2, 3}       // [1 2 3 0 0]
arr5 := [5]int{3: 4}          // [0 0 0 4 0]
arr6 := [5]int{2: 5, 6, 1: 7} // [0 7 5 6 0]

API

val := arr[5] // read from array
arr[5] = 100  // write to the array
len(arr)      // get length
cap(arr)      // get capacity
p := &arr     // get pointer to the array
s := arr[1:4] // get slice from the array

Examples

Out of bounds

arr := [3]int{1, 2, 3}
idx := 4
arr[idx] // panic: runtime error: index out of range [4] with length 3
arr[4]   // compilation error: invalid argument: index 4 out of bounds [0:3]

В go есть BCE (Bound Check Elimination), который, по сути, нужен для предотвращения записи/чтения в соседние (чужие) ячейки памяти.

Negative indexes

arr := [3]int{1, 2, 3}
idx := -1
arr[idx]  // panic: runtime error: index out of range [-1]
arr[-1]   // compilation error: invalid argument: index -1 (constant of type int) must not be negative

Length

arr := [10]int{}
len(arr) // 10

Capacity

var arr [10]int
cap(arr) // 10

Array comparison

arr1 := [...]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
 
// Except array whose element types ar incomparable types
arr1 == arr2 // true
arr1 != arr2 // false
// But [<, <=, >, >=] --> compilation error: invalid operation: arr1 > arr2 (operator > not defined on array)

Empty array

var arr [10]byte
fmt.Println(unsafe.Sizeof(arr)) // 10 (size in bytes)

Zero array

var arr [0]byte
fmt.Println(unsafe.Sizeof(arr)) // 0 (size in bytes)

Negative array

var arr [-1]int // compilation error: invalid array length -1 (untyped int constant)

Variable or constant as a size of array

length1 := 100
var arr1 [length1]int // compilation error: invalid array length length1
 
length2 := 100
var arr2 [length2]int // ok

Компилятору нужно при компиляции понимать, какого размера будет массив, поэтому размер может быть только константой или константным значением (константы известны компилятору на этапе компиляции).

make()

_ = make([10]) // compilation error: [10]int{} is not a type

append()

_ = append([10]int{}, 10) // compilation error: first argument to append must be a slice; have [10]int{} (value of type [10]int)

Array indexing mechanism in Go

arr[idx] = *(arr + idx * elemSize)

arrуказатель на начало
idxиндекс элемента, к которому обращаешься
elemSizeразмер элемента, к которому обращаешься

То есть:

  • arr[0] = *(arr + 0 * 2) // *(0xA02 + 0 * 2) = 0xA2
  • arr[0] = *(arr + 1 * 2) // *(0xA02 + 1 * 2) = 0xA4
  • arr[0] = *(arr + 2 * 2) // *(0xA02 + 2 * 2) = 0xA6

Для наглядности можно напрямую применить показанную формулу для операций по индексу, то есть, сделать работу за компилятора:

const elemSize = unsafe.Sizeof(int32(0)) // 4 bytes
arr := [...]int32{1, 2, 3}
pointer := unsafe.Pointer(&arr)
 
first := *(*int32)(unsafe.Add(pointer, 0*elemSize))  // arr[0]
second := *(*int32)(unsafe.Add(pointer, 1*elemSize)) // arr[1]
third := *(*int32)(unsafe.Add(pointer, 2*elemSize))  // arr[2]
 
dangerous1 := *(*int32)(unsafe.Add(pointer, -1)) // arr[-1]
dangerous2 := *(*int32)(unsafe.Add(pointer, 3))  // arr[3]
 
fmt.Println("Correct:", first, second, third)     // Correct: 1 2 3
fmt.Println("Dangerous:", dangerous1, dangerous2) // Dangerous: 256 512

Length is part of the array type

Длина массива (как и размер элементов) является частью типа (не хранится рядом с данными).
Длина массива — информация, которая доступна у типа данных массива; другими словами, длина “зашита” в типе данных.

arr1 := [3]int{10, 20, 30}
arr2 := [2]int{10, 20}
 
arr1 = arr2 // compilation error: cannot use arr2 (variable of type [2]int) as [3]int value in assignment

arr2 нельзя присвоить arr1 потому, что это разные типы данных. 3 в [3]intчасть типа данных (ошибка компиляции как раз на это и указывает).

Вызов len() для массива вычисляется на этапе компиляции

Ибо длина известна компилятору на этапе компиляции и значение переменной length ниже будет вычислена уже на этапе компиляции (а не в рантайме):

arr := [4]int{1, 2, 3, 4}
length := len(arr) // 4

Iteration over an array

C style

for i := 0; i < len(arr); i++ {
    // ...
}

Используя С style loops стоить помнить про Off-by-one error.
range как раз позволит избежать этой ошибки.

range

for _, val := range arr {
    // ...
}
for idx, _ := range arr {
    // ...
}
// или просто:
for idx := range arr {
    // ...
}
for _, _ := range arr {
    // ...
}
// или просто:
for range arr {
    // ...
}

Examples

Modifying array elements during iteration
values := [...]int{100, 200, 300}
 
// Also relevant for slices
for idx, val := range values {
	val += 50
 
	fmt.Printf("#1: %p #2: %p\n", unsafe.Pointer(&val), unsafe.Pointer(&values[idx]))
}
 
fmt.Printf("values: %v\n", values)
 
/*
	#1: 0xc000010180 #2: 0xc00001c1b0
	#1: 0xc0000101a0 #2: 0xc00001c1b8
	#1: 0xc0000101a8 #2: 0xc00001c1c0
	values: [100 200 300]
*/

Как видно, val += 50 в теле цикла не изменил элементы values.
Так происходит потому, что в Go всё копируется, то есть, val += 50 изменяет локальную копию элемента, а не сам элемент. В выводе видно, что адрес val никак не связан с адресом элементов values.

Еще по выводу видно, что val создается не единожды (адрес в каждой итерацииразный).
В Go 1.22+ уникальный экземпляр будет создан для каждой итерационной переменной в каждой итерации цикла.

Correct way to modify array elements during iteration

Самый простой вариант — использование индексов для модификации элемента:

for idx := range values {
	values[idx] += 50
}

Можно воспользоваться и указателями в некоторых случаях, но стоит учесть, что этот вариант будет медленее первого варианта с индексами:

type account struct {
	balance int
}
accounts := [...]*account{
	{balance: 100},
	{balance: 200},
	{balance: 300},
}
 
for _, a := range accounts {
	a.balance++
}

Sauce: Видос про массивы и слайсы в Go от Владимира Балуна