💻 ПРОГРАММИРОВАНИЕ

Стек и куча: где программа хранит свои данные и почему это важно

У каждой работающей программы есть два склада для данных. Один — быстрый и аккуратный, как стопка тарелок. Другой — огромный и свободный, но требует ручного учёта. От того, куда попадают ваши данные, зависит скорость и надёжность программы.

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

Когда программа запускается, операционная система выделяет ей кусок памяти. Внутри этого куска есть несколько областей, но два героя нашего рассказа — стек и куча. Разница между ними объясняет добрую половину ошибок и тормозов, с которыми сталкиваются программисты.

Стек: дисциплина стопки тарелок

Стек устроен по принципу «последним пришёл — первым ушёл». Каждый раз, когда вызывается функция, на стек кладётся новый кадр (frame): в нём живут локальные переменные функции и адрес, куда вернуться. Функция закончила работу — её кадр снимается со стека целиком, мгновенно.

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

def b():
    y = 2      # кадр b: переменная y
    return y

def a():
    x = 1      # кадр a: переменная x
    return b() # поверх кадра a ложится кадр b

print(a())

Пока работает b, на стеке лежат оба кадра. Как только b вернула значение, её кадр исчезает, и остаётся только a. Размер стека ограничен — обычно несколько мегабайт. Если переусердствовать (например, бесконечной рекурсией), стек переполнится, и программа рухнет с ошибкой «stack overflow».

Куча: свобода со своей ценой

А что, если вам нужны данные, которые должны пережить функцию, где они созданы? Или объект, размер которого вы узнаёте только во время работы? Тогда на сцену выходит куча — большая область памяти, из которой можно «откусить» блок нужного размера в любой момент.

Куча гораздо просторнее стека и гибче: данные в ней живут ровно столько, сколько нужно, независимо от вызовов функций. Но за гибкость приходится платить. Во-первых, выделение места в куче медленнее — нужно найти подходящий свободный кусок. Во-вторых, кто-то должен освобождать память, когда она больше не нужна.

Кто убирает за нами

Здесь языки расходятся. В C и C++ вы запрашиваете память вручную и так же вручную возвращаете. Забыли вернуть — получите утечку памяти: программа держит блоки, которыми уже не пользуется, и постепенно съедает всю память.

В Python, Java, JavaScript и многих других языках за вас следит сборщик мусора — фоновый механизм, который находит данные, на которые больше никто не ссылается, и освобождает их. Удобно, но не бесплатно: сборка мусора иногда подъедает производительность в неподходящий момент.

Что куда попадает

СтекКуча
Локальные переменные функцийОбъекты и большие структуры
Аргументы и адреса возвратаДанные с заранее неизвестным размером
Живёт, пока работает функцияЖивёт, пока на неё кто-то ссылается
Очень быстро, автоматическиМедленнее, требует учёта

Почему это важно вам

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

Большинству программистов не нужно вручную раскладывать данные по стеку и куче — язык делает это за них. Но знать, где живут ваши данные, — значит понимать, почему программа ведёт себя именно так, а не иначе. Это разница между «магия сломалась» и «ага, кончился стек».

#как устроено#куча#память#программирование#стек