TGViewer
Николай Тузов Николай Тузов @ntuzov · 16.9K subscribers
Post #785 11.9K
👴 Внутреннее устройство памяти — что такое Stack и зачем он нужен?

Что ж, продолжаю традиционно готовить вас к новому большому ролику сильно заранее перед его выходом. Когда он будет? Пока рано говорить, не хочу вас обмануть. Точно не в ближайшее время. О чём? Можете догадаться 🥂

Представим, что мы проектируем язык программирования и нужно решить: где хранить локальные переменные функций?

Допустим, у нас есть функция A() (main(), если хотите). У неё есть свои локальные переменные, которые больше никому не доступны, и их надо где-то разместить.

Мы любим простоту, поэтому начнём с самого простого решения — давайте просто выдадим A() какой-то адрес в памяти, например такой: 0x1000. Далее она сама распределит адреса для своих переменных, начиная с этой точки:

func A() {
x := 1 // 0x1000
y := 2 // 0x1008
}


И это отлично работает! Компилятор видит две переменные, резервирует 16 байт, всё просто.

————

Добавим вторую функцию

Теперь A вызывает B. Какой адрес дать B?

func A() {
x := 1 // 0x1000
y := 2 // 0x1008
B() // какой адрес?
}

func B() {
a := 10 // ???
b := 20 // ???
}


0x1000 не подходит — там уже лежат x и y функции A, которые ей ещё понадобятся после возврата из B. Затрём их — сломаем программу 😩

Самое простое и очевидное решение: выделенный для B адрес должен начинаться после адреса A. A занимает 16 байт: 0x1000 + 15 = 0x100F, за ним идёт 0x1010 (hex представление). Значит B начнётся с 0x1010:

func A() {
x := 1 // 0x1000
y := 2 // 0x1008
B() // начнётся с 0x1010
}

func B() {
a := 10 // 0x1010
b := 20 // 0x1018
}


Если в цепочке вызовов появляются новые функции, действуем по аналогии.

Итого получаем следующую картину:

// Указатель: 0x1000

func A() {
// Начинаем с текущей позиции: 0x1000
x := 1 // 0x1000
y := 2 // 0x1008
// Занимает 16 байт, сдвигаем указатель: 0x1000 → 0x1010
B()
}

func B() {
// Начинаем с текущей позиции: 0x1010
a := 10 // 0x1010
b := 20 // 0x1018
// Занимает 16 байт, сдвигаем: 0x1010 → 0x1020
C()
}

func C() {
// Начинаем с текущей позиции: 0x1020
k := 100 // 0x1020
}


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

Вернее, до того самого момента stack overflow 🙃
Теперь вы понимаете что это такое — если цепочка слишком длинная, стек не влезает в выделенную под него память и программа падает со знаменитой ошибкой.


————

А как освобождать память?

Когда C завершается, её память больше не нужна. Хотим вернуть указатель назад. Но на сколько? На размер C? Нет, потому что внутри C могли вызваться другие функции!

Правильный способ: каждая функция запоминает, где она начиналась, и при завершении возвращает указатель туда:

func A() {
saved := 0x1000 // запомнили начало A
// сдвинули: 0x1000 → 0x1010
x := 1
y := 2
B()
// B завершилась, возвращаем: 0x1010 → 0x1000
}

func B() {
saved := 0x1010 // запомнили начало B
// сдвинули: 0x1010 → 0x1020
a := 10
b := 20
C()
// C завершилась, возвращаем: 0x1020 → 0x1010
}

func C() {
saved := 0x1020
// сдвинули: 0x1020 → 0x1028
k := 100
// возвращаем: 0x1028 → 0x1020
}


————

Таким образом указатель растёт при вызовах и возвращается при завершении. Последняя вызванная функция первой освобождает память. Узнаёте? Это же классическая структура LIFO — Last In, First Out!

И теперь нас можно поздравить, ведь мы изобрели тот самый стек — вот так просто, принимая очевидные минималистичные решения. Такая структура получилась естественным образом из логики адресации.

————

Но что если у нас появляется переменная, которая будет доступна в разных функциях? Такое часто случается когда мы работаем с указателями. И это уже другая история.. Обсудим это в отдельном посте.

#guide #memory
  • 🔥 152
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 →