← Назад к вопросам

Для чего нужен стек как область памяти?

2.0 Middle🔥 221 комментариев
#Умные указатели и управление памятью#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Стек — область памяти для локальных переменных и управления потоком выполнения

Стек — это критически важная область памяти, которая управляет функциями, локальными переменными и контролем потока программы. Без стека программа не может работать.

Назначение стека

1. Хранение локальных переменных

Каждой функции нужно где-то хранить свои локальные переменные. Стек идеален для этого:

void function() {
    int x = 5;           // На стеке
    double y = 3.14;     // На стеке
    std::string name;    // На стеке (но сами данные строки могут быть в heap)
}  // Когда функция заканчивается, x, y, name автоматически удаляются

2. Управление вызовом функций

Стек хранит адрес возврата и контекст вызова:

void function_a() {
    int x = 10;
    function_b();      // Адрес возврата сохраняется на стек
    // После возврата из function_b продолжаем отсюда
}

void function_b() {
    int y = 20;
    // На стеке: [адрес возврата в function_a] [y = 20]
}

3. Передача параметров

Параметры функции (обычно) передаются через стек:

int add(int a, int b) {  // a и b на стеке
    return a + b;
}

add(5, 3);  // Аргументы 5 и 3 помещаются на стек

Как работает стек

Стек растёт вниз (в большинстве архитектур x86):

Высокий адрес памяти
    ↓
[локальные переменные function_a]
    ↓
[адрес возврата из function_a]
    ↓
[локальные переменные function_b]  ← Stack Pointer (SP/RSP)
    ↓
Низкий адрес памяти

LIFO (Last In First Out) структура:

void outer() {
    int x = 1;              // Шаг 1: x на стек
    inner();                // Шаг 2: вызов inner()
}                           // Шаг 4: возврат из inner()

void inner() {
    int y = 2;              // Шаг 2: y на стек (поверх x)
    // Стек: [x=1][y=2] ← SP
}                           // Шаг 3: y удаляется со стека

Операции со стеком

PUSH — добавить значение:

push rbx        ; Поместить RBX на стек, уменьшить RSP на 8 байт
void push_example() {
    int a = 10;     // PUSH a
    int b = 20;     // PUSH b
    // Стек: [...][a=10][b=20] ← SP
}

POP — удалить значение:

pop rbx         ; Прочитать со стека в RBX, увеличить RSP на 8 байт
void pop_example() {
    int a = 10;
    int b = 20;
}   // POP b, затем POP a

Сравнение: Стек vs Heap

Стек:

  • Автоматическое управление памятью ✓
  • Очень быстро (просто сдвинуть указатель) ✓
  • Ограниченный размер ✗
  • LIFO порядок ✗
  • Область видимости (scope) ✓

Heap:

  • Ручное управление (delete/free) ✗
  • Медленнее (алгоритмы выделения) ✗
  • Практически неограниченный размер ✓
  • Произвольный порядок доступа ✓
  • Глобальный доступ ✓
void memory_regions() {
    // На СТЕКЕ:
    int stack_var = 5;                          // Быстро, автоматически удалится
    std::array<int, 100> array;                 // На стеке (ограничено размером)
    
    // На HEAP:
    int* heap_var = new int(5);                 // Медленно, нужно delete
    int* heap_array = new int[1000000];        // На heap (много памяти)
    
    // Смешанное:
    std::vector<int> vec;                       // Сам вектор на стеке, данные на heap
    std::string str = "hello";                  // Объект на стеке, строка на heap
    
    delete heap_var;                            // Вручную удаляем
    delete[] heap_array;
}  // stack_var, array, vec, str автоматически удаляются

Важность стека для программы

1. Рекурсия работает благодаря стеку:

int factorial(int n) {
    if (n <= 1) return 1;    // Base case
    return n * factorial(n-1);  // Recursive call
}

// factorial(5):
// Стек при n=3: [n=5][return addr][n=4][return addr][n=3][return addr] ← SP

2. Контекст функции сохраняется:

void a() {
    int x = 10;
    b();           // Адрес возврата сохранен на стек
    std::cout << x;  // После b(), x ещё на стеке и имеет значение 10
}

void b() {
    int y = 20;    // Новая переменная на стек
}  // y удаляется, x всё ещё там

3. Exception unwinding:

try {
    function_that_throws();  // Стек: [exception]
} catch (...) {
    // Все локальные переменные между throw и catch удаляются
    // Деструкторы вызываются в обратном порядке (stack unwinding)
}

Проблемы со стеком

1. Stack overflow — переполнение стека

void infinite_recursion() {
    int big_array[1000000];  // Каждый вызов — большой размер на стеке
    infinite_recursion();     // Рекурсия без выхода
}  // Stack overflow! Стек закончился

// Результат: Segmentation fault

2. Ограниченный размер

Типичный размер стека: 1-8 МБ (в зависимости от ОС).

int main() {
    int huge_array[100000000];  // 400 МБ на стеке!
}  // Stack overflow!

// Правильно:
int main() {
    std::vector<int> huge_array(100000000);  // На heap, безопасно
}

3. Использование памяти после удаления (в C)

int* bad_function() {
    int local = 10;
    return &local;  // Возвращаем адрес на стек!
}  // local удаляется, указатель теперь invalid!

int main() {
    int* ptr = bad_function();
    std::cout << *ptr;  // Undefined behavior!
}

Размер стека на разных платформах

# Linux
ulimit -s  # Обычно 8192 KB = 8 MB

# macOS
ulimit -s  # Обычно 8192 KB = 8 MB

# Windows
# По умолчанию 1 MB (но можно увеличить при линковке)

# Увеличить стек в Linux:
ulimit -s unlimited  # Или конкретное значение в KB
ulimit -s 16384      # 16 MB

Практический пример: что на стеке, что на heap

struct Person {
    std::string name;      // Сам объект на стеке, строка на heap
    int age;               // На стеке
    int* salary;           // Указатель на стеке, данные на heap
};

int main() {
    // На СТЕКЕ:
    Person p1{"Alice", 30, new int(5000)};
    // Адреса стека:
    // [p1.name объект] [p1.age=30] [p1.salary указатель]
    
    // На HEAP:
    Person* p2 = new Person{"Bob", 25, new int(6000)};
    // На стеке только: [p2 указатель]
    // На heap: [весь объект Person]
    
    delete p2->salary;
    delete p2;  // Очищаем heap
}  // Стек очищается автоматически (p1 и p2 удаляются)

Оптимизация: используй стек когда возможно

// ❌ Неэффективно - алокация на heap
std::unique_ptr<std::array<int, 10>> arr(new std::array<int, 10>);

// ✓ Правильно - на стеке (быстрее и проще)
std::array<int, 10> arr;  // На стеке, нет алокации

// ✓ Правильно для больших размеров
std::vector<int> big_array(1000000);  // На heap

Вывод

  • Стек — область памяти для локальных переменных и контроля функций
  • Автоматическое управление: очень быстро
  • LIFO структура: последняя функция первая возвращается
  • Ограниченный размер: 1-8 МБ обычно
  • Стек переполняется рекурсией и большими локальными массивами
  • Для больших данных используй heap (vector, string, новые объекты)
  • Стек незаменим для работы программы: без него функции не могут вызываться