Содержание
Segmentation fault, или ошибка сегментации, — аварийное завершение программы, которая обратилась к памяти, не принадлежащей ей. Операционная система замечает это и останавливает программу. Компилируется такой код без ошибок: проблема проявляется только при запуске.
Как выглядит ошибка
В Linux и macOS после падения в терминале появляется короткая строка:
Segmentation fault (core dumped)
Код завершения такой программы — 139. В Windows то же самое называется «нарушение прав доступа» (access violation) с кодом 0xC0000005. В CLion в конце вывода Вы увидите Process finished with exit code 139 (interrupted by signal 11: SIGSEGV).
Ни номера строки, ни имени переменной в сообщении нет. Поэтому главная трудность — не исправить ошибку, а найти её.
Почему компилятор не предупреждает
С точки зрения языка обращение к чужой памяти — неопределённое поведение. Стандарт не обещает, что программа упадёт: она может вывести мусор, испортить другую переменную или сработать «правильно» и сломаться через месяц на другом компьютере. Падение с segmentation fault — ещё удачный исход: ошибка хотя бы заметна.
Причина 1. Нулевой или неинициализированный указатель
int* p = nullptr;
*p = 42; // записи по нулевому адресу не существует
То же произойдёт, если указателю вообще не дали значения: в нём лежит случайный адрес.
int* p;
*p = 42; // адрес неизвестен
Решение: давайте указателю значение при объявлении и проверяйте его перед использованием.
if (p != nullptr) {
*p = 42;
}
Причина 2. Выход за границы массива
int data[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; ++i) { // должно быть i < 5
std::cout << data[i] << '\n';
}
В массиве из пяти элементов индексы идут от 0 до 4. Элемента data[5] нет. Чтение за границей часто проходит незаметно и даёт мусор, а запись портит соседние данные или роняет программу. Это самая частая причина ошибки сегментации у начинающих.
Решение: проверяйте условия циклов и пользуйтесь std::vector с методом at(). Он проверяет индекс и при ошибке бросает исключение std::out_of_range с понятным сообщением:
std::vector<int> scores = {5, 4, 3};
std::cout << scores.at(10) << '\n'; // исключение вместо порчи памяти
Причина 3. Ссылка или указатель на уничтоженный объект
Локальная переменная исчезает, когда функция завершается. Ссылка на неё остаётся, но указывает в никуда.
int& lastScore() {
int score = 5;
return score; // score исчезнет при выходе из функции
}
Компилятор здесь предупреждает: reference to stack memory associated with local variable 'score' returned. Не пропускайте такие сообщения.
Та же ошибка с динамической памятью называется «использование после освобождения»:
int* p = new int(5);
delete p;
std::cout << *p << '\n'; // памяти уже нет
Решение: возвращайте из функции значение, а не ссылку на локальную переменную. Вместо new и delete используйте std::vector, std::string и умные указатели — они освобождают память сами и вовремя.
Причина 4. Ссылки и итераторы после изменения вектора
std::vector<int> numbers = {1, 2, 3};
int& first = numbers[0];
numbers.push_back(4); // вектор мог переехать в другое место памяти
std::cout << first << '\n'; // ссылка указывает на старое место
Когда вектору не хватает места, он выделяет новый участок памяти и переносит туда элементы. Все прежние ссылки, указатели и итераторы на элементы после этого недействительны.
Решение: не храните ссылки на элементы вектора, пока добавляете или удаляете элементы. Храните индекс.
Причина 5. Бесконечная рекурсия
int factorial(int n) {
return n * factorial(n - 1); // нет условия остановки
}
Каждый вызов функции занимает место в стеке. Рекурсия без условия остановки исчерпывает стек, и программа падает. Эту ситуацию называют переполнением стека (stack overflow).
Решение: у рекурсивной функции должна быть ветка, которая не вызывает её снова.
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
Причина 6. Слишком большой массив на стеке
int main() {
int grid[10000000]; // около 40 мегабайт на стеке
grid[0] = 1;
}
Размер стека ограничен — обычно несколькими мегабайтами. Большой локальный массив в него не помещается.
Решение: большие массивы храните в std::vector. Его элементы лежат в динамической памяти, где места намного больше.
std::vector<int> grid(10000000);
Причина 7. Запись в строковый литерал
char* text = (char*)"hello";
text[0] = 'H'; // строковые литералы изменять нельзя
Строковый литерал хранится в области памяти, доступной только для чтения.
Решение: для строк, которые нужно менять, используйте std::string.
Как найти место падения
AddressSanitizer
Это самый быстрый способ. Возьмём программу с ошибкой — в векторе три элемента, а читается четвёртый:
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores = {5, 4, 3};
int index = 3;
std::cout << scores[index] << '\n';
}
Соберите её с двумя дополнительными флагами:
g++ -g -fsanitize=address,undefined main.cpp -o app
./app
Вместо молчаливой ошибки программа печатает подробный отчёт:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000dc
READ of size 4 at 0x6020000000dc thread T0
#0 0x0001026f8b48 in main main.cpp:7
0x6020000000dc is located 0 bytes after 12-byte region
Читается он так: произошло чтение за границей участка памяти (heap-buffer-overflow), виновата седьмая строка файла main.cpp, обращение пришлось сразу за концом блока в 12 байт — то есть за концом вектора из трёх чисел int.
AddressSanitizer находит выход за границы, использование памяти после освобождения и обращение к уничтоженным локальным переменным. Работает в GCC и Clang на Linux и macOS; в Windows его поддерживает Visual Studio.
Отладчик
В CLion запустите программу кнопкой отладки (значок жука), а не обычным запуском. Когда программа упадёт, среда остановится на строке с ошибкой и покажет значения переменных и цепочку вызовов.
В терминале то же самое делает gdb:
g++ -g main.cpp -o app
gdb ./app
В gdb введите run, дождитесь падения и введите bt — отладчик покажет, какая функция и в какой строке выполнялась. В macOS вместо gdb используется lldb с теми же командами.
Предупреждения компилятора
Часть ошибок компилятор видит заранее. Собирайте учебные программы с флагами -Wall -Wextra и читайте всё, что он пишет: возврат ссылки на локальную переменную, использование неинициализированной переменной и бесконечная рекурсия попадают в предупреждения.
Как не допускать ошибку
- Давайте переменным и указателям значение при объявлении.
- Вместо обычных массивов используйте
std::vectorиstd::string. - В циклах по массиву пишите
i < size, а неi <= size. - Пока программа отлаживается, обращайтесь к элементам через
at(). - Не возвращайте ссылки и указатели на локальные переменные.
- Не пишите
newиdeleteбез необходимости. - Собирайте учебные программы с флагами
-Wall -Wextra -g -fsanitize=address,undefined.
Коротко
- Segmentation fault — обращение к памяти, которой у программы нет.
- Чаще всего виноваты выход за границы массива, нулевой указатель и ссылка на уничтоженный объект.
- Сообщение об ошибке не называет строку; её находят AddressSanitizer и отладчик.
- Контейнеры стандартной библиотеки и предупреждения компилятора предотвращают большую часть таких ошибок.
В стандарте C++26 появились проверки, которые ловят часть этих ошибок без дополнительных инструментов, — о них рассказано в статье Что нового в C++26. Если же программа не собирается вовсе, посмотрите разбор ошибки undefined reference.
Использованная литература
- Undefined behavior — cppreference.com [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/ub (дата обращения: 08.10.2026).
- AddressSanitizer — документация Clang [Электронный ресурс]. — URL: https://clang.llvm.org/docs/AddressSanitizer.html (дата обращения: 08.10.2026).
- ISO/IEC 14882:2024 — Язык программирования C++ [Электронный ресурс]. — URL: https://www.iso.org/standard/83626.html (дата обращения: 08.10.2026).