← Назад к вопросам

Что находится под слайсом?

1.2 Junior🔥 171 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Под капотом слайса в Go

Под слайсом в Go находится легковесная структура данных, которая предоставляет собой абстракцию над массивом с динамической длиной. Это не самостоятельная коллекция, а "окно" или "дескриптор" для работы с частью массива.

Структура слайса (Slice Header)

Внутренне слайс представлен структурой, содержащей три поля:

// Примерное представление (реализация в runtime)
type sliceHeader struct {
    Pointer uintptr // Указатель на первый элемент массива
    Length  int     // Текущая длина слайса (количество элементов)
    Capacity int    // Ёмкость слайса (максимальная длина без переаллокации)
}

Ключевые особенности:

  • Указатель (Pointer) - ссылается на конкретный элемент в массиве, с которого начинается слайс
  • Длина (Length) - количество элементов, доступных через слайс (len(slice))
  • Ёмкость (Capacity) - общее количество элементов в базовом массиве от начального элемента до конца массива (cap(slice))

Пример для наглядности

package main

import "fmt"

func main() {
    // Создаем массив
    arr := [6]int{10, 20, 30, 40, 50, 60}
    
    // Создаем слайс, указывающий на часть массива
    slice := arr[1:4] // Указывает на элементы 20, 30, 40
    
    fmt.Printf("Слайс: %v\n", slice)           // [20 30 40]
    fmt.Printf("Длина: %d\n", len(slice))     // 3
    fmt.Printf("Ёмкость: %d\n", cap(slice))   // 5 (от элемента 20 до конца массива)
    
    // Изменяем элемент через слайс
    slice[0] = 99
    fmt.Printf("Массив после изменения: %v\n", arr) // [10 99 30 40 50 60]
}

Важные аспекты реализации

1. Слайс - это ссылочный тип по поведению, но не по структуре

  • При передаче слайса в функцию передается копия slice header (3 поля)
  • Указатель внутри заголовка ссылается на тот же массив
  • Изменения элементов видны всем слайсам, использующим тот же базовый массив

2. Переаллокация (reallocation) при append

  • Когда append превышает capacity, создается новый массив
  • Старые данные копируются в новый массив большего размера
  • Новый слайс получает указатель на новый массив
func demonstrateReallocation() {
    slice := []int{1, 2, 3}
    fmt.Printf("До append: %p, len=%d, cap=%d\n", slice, len(slice), cap(slice))
    
    slice = append(slice, 4, 5, 6)
    fmt.Printf("После append: %p, len=%d, cap=%d\n", slice, len(slice), cap(slice))
    // Адрес может измениться из-за переаллокации
}

3. Нулевой слайс (nil slice)

var nilSlice []int
// sliceHeader{Pointer: 0, Length: 0, Capacity: 0}

4. Пустой, но не nil слайс

emptySlice := []int{}
// sliceHeader{Pointer: указатель на пустую память, Length: 0, Capacity: 0}

Практические следствия

Проблемы с производительностью:

  • Частые переаллокации при неправильной предварительной аллокации
  • "Утечки" памяти из-за удержания ссылок на большой массив через маленький слайс

Рекомендации по работе:

  1. Используйте make с указанием capacity при известном размере:

    // Плохо - будет несколько переаллокаций
    var slice []int
    for i := 0; i < 1000; i++ {
        slice = append(slice, i)
    }
    
    // Хорошо - одна аллокация
    slice := make([]int, 0, 1000)
    for i := 0; i < 1000; i++ {
        slice = append(slice, i)
    }
    
  2. Копируйте слайсы явно, если нужно разорвать связь с базовым массивом:

    original := []int{1, 2, 3, 4, 5}
    copy := make([]int, len(original))
    copy(copy, original) // Теперь изменения в copy не затрагивают original
    

Слайс против массива:

  • Массив - фиксированный размер, передается по значению (копируется)
  • Слайс - динамический размер, передается копией заголовка (дешевле)

Понимание внутреннего устройства слайса критически важно для написания эффективного Go-кода, избежания скрытых багов, связанных с неожиданным разделением данных, и оптимизации производительности при работе с коллекциями.