Ответ
Стек — это высокоскоростная, но ограниченная область памяти, непригодная для хранения всех данных из-за фундаментальных проектных ограничений:
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 память в куче держит, но если б не держал — тут бы нам всем пизда.
}
И вот поэтому существует куча, этакая гигантская, но слегка медлительная кладовка. Туда можно свалить что угодно и любого размера, жить это будет сколько захочет, а управлять им будем через ссылки. Плата за эту вольницу — скорость: каждый поход в эту кладовку (аллокация, деаллокация) требует времени и усилий. Зато не переполнишься.