Получить доступ

Чем стек отличается от кучи?

JuniorОперационные системы
Ещё спрашивают так:
  • Где хранятся локальные переменные и объекты?
  • Что такое StackOverflowError?
  • Почему выделение на стеке быстрее, чем в куче?
Коротко

Стек — память для вызовов функций: локальные переменные и адрес возврата, выделяется и освобождается автоматически при входе и выходе. Куча — память для данных, чьё время жизни не привязано к вызову.

  • Стек у каждого потока свой, небольшой (обычно 1–8 МБ), работает как LIFO
  • Выделение на стеке — сдвиг указателя, почти бесплатно
  • Куча общая для потоков; освобождает программист или сборщик мусора

А ещё Глубокая рекурсия переполняет стек — StackOverflowError, RecursionError

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

Дерево рекурсивных вызовов и мемоизация

Подробно

Стек вызовов. При вызове функции на стек кладётся кадр (stack frame): аргументы, локальные переменные, адрес возврата. При выходе кадр снимается. Память выделяется и освобождается сдвигом одного указателя, поэтому это очень быстро и не фрагментирует память. Ограничения: размер стека фиксирован при создании потока (в Linux по умолчанию 8 МБ для главного потока, в Java -Xss, обычно 512 КБ – 1 МБ), и данные живут только до выхода из функции.

Куча — область для объектов, которые должны пережить вызов или чей размер неизвестен заранее. Выделение сложнее: аллокатор ищет свободный блок подходящего размера, память фрагментируется. Освобождение — вручную (free, delete в C/C++), через подсчёт ссылок (Python, Swift) или сборщиком мусора (Java, Go, C#).

Что где лежит:

  • в C/C++ локальные массивы и структуры — на стеке, malloc/new — в куче;
  • в Java на стеке — примитивы и ссылки, сами объекты — в куче (JIT может разместить объект на стеке после escape-анализа, если он не «убегает» из метода);
  • в Python все объекты в куче, на стеке интерпретатора — ссылки;
  • в Go компилятор сам решает по escape-анализу.

Переполнение стека. Каждый рекурсивный вызов добавляет кадр. Без базового случая или при глубине в десятки тысяч стек кончается: StackOverflowError в Java, RecursionError в Python (там лимит глубины 1000 по умолчанию стоит раньше реального стека). Лечится переписыванием на цикл с явным стеком.

Потоки. Стек — у каждого потока свой, поэтому локальные переменные потокобезопасны сами по себе. Куча общая — объект, на который ссылаются два потока, нужно синхронизировать.

Не путать со структурами данных: «куча» как область памяти и двоичная куча (heap) — разные вещи с одинаковым названием.

Пример

import sys
sys.getrecursionlimit()          # 1000

def depth(n):
    return 0 if n == 0 else 1 + depth(n - 1)

depth(500)                       # работает
depth(5000)                      # RecursionError: maximum recursion depth exceeded

def depth_loop(n):               # тот же результат без роста стека
    d = 0
    while n: n -= 1; d += 1
    return d

Что хотят услышать

  • Стек — кадры вызовов, LIFO, автоматическое освобождение, свой у потока.
  • Куча — объекты с произвольным временем жизни, общая, GC или ручное освобождение.
  • Почему стек быстрее и почему он ограничен.
  • Переполнение стека при рекурсии и как его избежать.

Частые ошибки

  • Путать область памяти «куча» со структурой данных «куча».
  • Говорить, что в Java объекты бывают на стеке «если маленькие».

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем