TGViewer
Николай Тузов Николай Тузов @ntuzov · 16.9K subscribers
Post #787 14.6K
🤓 Продолжаем изобретать память — что такое Heap и зачем это нужно

В прошлом посте мы придумали стек для локальных переменных функций. Всё работает отлично, но что делать, если переменная должна жить дольше функции?

————

Допустим, функция создаёт объект и возвращает адрес на него:

func CreateUser() *User {
// начинается с 0x1008
user := User{name: "Bob"} // 0x1008
// указатель: 0x1008 → 0x1028
return &user // возвращаем адрес 0x1008
// функция завершилась → указатель: 0x1028 → 0x1008
}

func main() {
// начинаем с 0x1000
var u *User // 0x1000
// указатель: 0x1000 → 0x1008

u = CreateUser() // получили адрес 0x1008
// CreateUser завершилась → указатель вернулся: 0x1028 → 0x1008

DoSomething() // начинается с 0x1008

fmt.Println(u.name) // 💩 читаем из 0x1008, но там уже другие данные
}


Что произошло? CreateUser() создала user в своём стековом фрейме и вернула на него адрес. Но когда функция завершилась, её стековый фрейм освободился — переменная user и её данные удалены.

Теперь u в main() указывает на невалидную память. Что там лежит? Никто не знает!

Очевидно, стек не подходит для данных, которые должны пережить функцию. Что делать?

————

Попытка 1: Глобальная память

Может просто выделить отдельную область для таких данных?

var globalUsers [1000]User  // зарезервировали 1000 мест
var nextIndex = 0

func CreateUser() *User {
user := &globalUsers[nextIndex]
nextIndex++
user.name = "Bob"
return user // безопасно - не в стеке
}


Это работает, но есть проблема: размер фиксирован. Нужно заранее угадать сколько объектов понадобится. 1000? 10000? А если миллион? А если в одной программе нужно 10, а в другой — миллион?

Плюс мы никогда ничего не освобождаем — даже если объект больше не нужен, его слот занят навсегда.

Нужно что-то более гибкое.

————

Решение: отдельная управляемая область

Да, вот такой резкий переход. Ну а как вы хотели? Это Telegram-пост, мнг ткста не влзт 😩

Давайте выделим большую область памяти отдельно от стека, где сможем:
- Выделять память когда нужно, динамически
- Любого размера
- Освобождать когда больше не нужно

Назовём эту область "куча" (heap):

// Стек: 0x1000..0x2000
// Куча: 0x10000..0x100000 (отдельная большая область)

func CreateUser() *User {
// Выделяем в куче
addr := allocate(32) // получили адрес 0x10100
user := (*User)(addr)
user.name = "Bob"
return user // 0x10100 - безопасно, не в стеке
}

func main() {
u := CreateUser() // 0x10100
DoSomething() // не затрёт - user в куче, не в стеке!
fmt.Println(u.name) // работает!
free(u) // освободили когда не нужно
}


Что ж, так гораздо лучше — данные живут независимо от вызовов функций.

————

Почему именно "куча"?

Важно: не путайте heap-память с heap-структурой данных (бинарной кучей для priority queue) - это совершенно разные вещи с одинаковым названием!

Название heap-память получила в противопоставление stack:
- Stack (стопка) — берём только сверху, строгий LIFO-порядок
- Heap (куча) — берём откуда угодно, любой порядок

В стеке память строго упорядочена. В куче же память выделяется и освобождается в любом порядке.

————

Цена гибкости

Куча решает проблему, но создаёт новые:

1. Медленно: allocate() ищет свободное место, управляет списками блоков
2. Фрагментация: память становится "дырявой" - много мелких свободных кусков вместо больших
3. Утечки памяти: забыл free() → ячейка занята навсегда → программа жрёт всё больше памяти.

Последнюю проблему решает сборщик мусора (GC). Вот о нём мы и поговорим в моём будущем ролике (подпишитесь, чтобы не пропустить 👍).

————

Итого: Стек vs Куча

Стек:
- Локальные переменные
- Автоматическое управление
- LIFO, очень быстро
- Ограничен по размеру

Куча:
- Динамические данные
- Ручное управление или GC
- Произвольный порядок, медленнее
- Большая

Теперь вы понимаете, почему в Go переменные иногда "убегают" (escape) в кучу — компилятор видит, что переменная используется после завершения функции, и автоматически выделяет её в куче вместо стека.

#guide #memory
  • 🔥 88
  • 😁 1
More from @ntuzov
  1. Sep 15, 2026Дописал самый большой кусок статьи про Go 1.27 — раздел про постквантовые подписи. В 1.27…
  2. Sep 13, 2026Нельзя мне браться за обзоры релизов... Вместо небольшого раздела по постквантновые подпис…
  3. Aug 27, 2026😐 Ультимативный разбор... Go 1.27? Честно, не знаю зачем я это сделал, но я сделал.. Ну п…
  4. Aug 24, 2026👴 Посоветуйте хорошего бухгалтера на аутсорс в Казахстане, в идеале в Астане Рынок у нас…
  5. Jul 25, 2026🦄 Опубликовал платформу для гайдов и первую статью https://golang.guide/ Надеюсь, вам пон…
  6. Jul 24, 2026В последние дни работаю чуть ли не по 16 часов над этим проектом — мой перфекционизм не да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →