Почему невозможно хранить все данные в стеке?

Ответ

Стек — это высокоскоростная, но ограниченная область памяти, непригодная для хранения всех данных из-за фундаментальных проектных ограничений:

1. Ограниченный и фиксированный размер: Размер стека потока задается при его создании (порядка нескольких МБ) и не может динамически расширяться. Хранение больших массивов или структур приведет к переполнению стека (Stack Overflow).

2. Время жизни, привязанное к области видимости: Память в стеке автоматически освобождается при выходе из функции (области видимости). Невозможно создать объект в функции и вернуть ссылку на данные в стеке — они будут сразу невалидны.

3. Отсутствие динамичности: Размер и layout стека должны быть известны на этапе компиляции. Невозможно выделить память под объект, размер которого определяется во время выполнения.

Пример иллюстрации проблемы:

func riskyFunction() {
    // Попытка выделить большой массив.
    // На практике Swift для Array использует кучу, но если бы это был стек:
    var hugeArrayOnStack = Array<Int>(repeating: 0, count: 1_000_000) // ~8 MB.
    // Высокая вероятность переполнения стека типичного потока (например, 2 MB).
}

Роль кучи: Куча решает эти проблемы, позволяя динамически выделять блоки памяти произвольного размера с независимым временем жизни, управляемым через ссылки и ARC. За это приходится платить производительностью.

Ответ 18+ 🔞

Ну слушай, вот представь себе стек — это как твой рабочий стол, только дохуя быстрый, но размером с листок А5, ёпта. На нём можно быстро набросать заметки, пока ты в функции сидишь, но как только вылез — всё, хуяк, и заметки в мусорку. А теперь попробуй на этот листок А5 вывалить кипу бумаг в метр высотой. Что будет? Правильно, пиздец, всё на пол упадёт. Это и есть переполнение стека, Stack Overflow, мать его.

Вот тебе три главные причины, почему на этом листочке всю жизнь не проживёшь:

1. Размер — хуй да маленький. Каждому потоку дают его личный стек, размером обычно пару мегабайт. И всё. Хочешь больше? Не, сука, не дадут. Это как пытаться в карманы джинсов запихнуть арбуз — нихуя не выйдет, только штаны порвёшь.

2. Время жизни — как у мотылька. Выделил память в стеке внутри функции — и живи с этим, пока функция не кончилась. Как только выполнение дошло до закрывающей фигурной скобки — всё, пизда, память автоматом подчистили. Хотел вернуть ссылочку наружу? А хута там! Вернёшь указатель на уже освобождённую память, и потом будешь гадать, почему программа ведёт себя как пьяный ёжик.

3. Динамичность — ноль ебать. Компилятор должен чётко знать, сколько места тебе нужно, ещё до запуска программы. Скажешь: «А я хочу массив, размер которого пользователь введёт» — так нет же, блядь, стек так не умеет. Всё должно быть известно заранее, иначе никакого тебе layout’а.

Вот, смотри, наглядный пиздец:

func riskyFunction() {
    // Допустим, Swift вдруг решит тут всё в стек пихать (хотя он так не делает).
    var hugeArrayOnStack = Array<Int>(repeating: 0, count: 1_000_000) // Это ж ~8 мегабайт!
    // А стек потока, допустим, всего 2 МБ. Ёпта, концерт!
    // На деле-то Array память в куче держит, но если б не держал — тут бы нам всем пизда.
}

И вот поэтому существует куча, этакая гигантская, но слегка медлительная кладовка. Туда можно свалить что угодно и любого размера, жить это будет сколько захочет, а управлять им будем через ссылки. Плата за эту вольницу — скорость: каждый поход в эту кладовку (аллокация, деаллокация) требует времени и усилий. Зато не переполнишься.