В Go длина массивов фиксируется как часть типа на момент компиляции.
// псевдокод ,примерно так будет выглядеть массив в памяти
var My5IntArray = Array {
len: 5 // количество элементов
elem: int // тип элементов
}В примере мы определили, что у нас будет массив длиной из 5 элементов, компилятор в момент сборки «записывает» это значение.
А при выполнении кода метод len(a) просто отдает заранее записанное значение.
Чтобы понять, как это работает на практике, давайте рассмотрим работу пакета types:
package types
// https://github.com/golang/go/blob/master/src/go/types/array.go
// An Array represents an array type.
type Array struct {
len int64
elem Type
}
// NewArray возвращает структуру Array, которая содержит тип элементов и длину a new array type for the given element type and length.
func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }
// Возвращает длину массива
// Выходное значение может быть отрицательным, это будет обозначать, что длина массива неизвестна.
func (a *Array) Len() int64 { return a.len }
// Возвращает тип элемента массива.
func (a *Array) Elem() Type { return a.elem }
// возвращает базовый тип, в нашем примере это будет [5]int
func (a *Array) Underlying() Type { return a }
// возвращает базовый тип в виде строки, в нашем примере это будет [5]int
func (a *Array) String() string { return TypeString(a, nil) }Важно, что этот код нужен для компилятора и анализа типов, он не используется для работы массивов в памяти и runtime.
А что же тогда используется?
Возьмем следующий код:
package main
func main() {
myArray := [3]int32{1, 2, 3}
_ = myArray
}Выполним следующие команды, чтобы собрать и дизасемблировать нашу программу:
// Соберем программу с отладочной информацие
go build -gcflags="-N -l" -o app
// дизассемблируем наш бинарник
go tool objdump app > dump.txtВ файле dump.txt найдем примерно следующий блок кода, которая отвечает за выполнение функции main в файле main.go
// находим секцию
TEXT main.main(SB) /path/to/main.go
// Нас интересует
main.go:4
// Подготавливаем память
0x10006746c f800c3ff MOVD ZR, 12(RSP)
0x100067470 b90017ff MOVW ZR, 20(RSP)
// Последовательно записываем значения массива
0x100067474 d2800de0 MOVD $1, R0
0x100067478 b9000fe0 MOVW R0, 12(RSP)
0x10006747c d2801bc0 MOVD $2, R0
0x100067480 b90013e0 MOVW R0, 16(RSP)
0x100067484 d28029a0 MOVD $3, R0
0x100067488 b90017e0 MOVW R0, 20(RSP)Как вы можете заметить, мы заранее выделяем память, для того, чтобы элементы массива находились рядом.
Обратите внимание что если бы мы присваивали бы значения последовательно идущим переменным, то получили бы примерно такие же инструкции, но не подготавливали бы участок памяти заранее, что в режиме включенной оптимизации машинных инструкций может приветси к тому, что значения отдельных переменных будут находиться “далеко” друг от друга в памяти. Рассмотрим пример с переменными:
// Исходный код
package main
func main() {
var a int64
var b int64
var c int32
a = 1
b = 2
c = 3
}
// находим секцию
TEXT main.main(SB) /path/to/main.go
// выделение памяти отсутствуем, сразу записываем значения
main.go:4
0x10006746c d2806120 MOVD $1, R0
0x100067470 f90013e0 MOVD R0, 32(RSP)
main.go:5
0x100067474 d2806f00 MOVD $2, R0
0x100067478 f9000fe0 MOVD R0, 24(RSP)
main.go:6
0x10006747c d2807ce0 MOVD $3, R0
0x100067480 f9000be0 MOVD R0, 16(RSP)Копирование массивов при передаче в функцию
Так как в Go при передаче в функцию все значения копируются, то при передаче массива в функцию мы не изменим исходный массив.
package main
import "fmt"
func modify(a [3]int) {
a[0] = 999
fmt.Println(a) // [999 2 3] — копия изменилась
}
func main() {
arr := [3]int{1, 2, 3}
fmt.Println(arr) // [1 2 3] — начальный массив
modify(arr)
fmt.Println(arr) // [1 2 3] — начальный массив не изменился
}
При передачи массива в функцию мы получим новую область памяти, в которой будем изменять значения. Визуально это будет выглядеть так:

Для того, чтобы мы могли изменить исходны массив, необходимо передать его по ссылке
package main
import "fmt"
func modify(a *[3]int) {
a[0] = 999
fmt.Println(a) // &[999 2 3] — копия изменилась
}
func main() {
arr := [3]int{1, 2, 3}
fmt.Println(arr) // [1 2 3] — начальный массив
modify(&arr)
fmt.Println(arr) // [999 2 3] — начальный массив не изменился
}
Также мы можем использовать slice, который основан на массиве, но это выходит за рамки данного материала и будет рассмотрено в следующих главах.
Leave a Reply