Глиф Учебники

Segmentation fault: почему программа падает

Справочник · C · 3 минуты чтения

Segmentation fault (ошибка сегментации) — программа обратилась к памяти, которая ей не принадлежит, и система её остановила. Это ошибка при работе, а не при сборке: компилятор её обычно не видит.

Как она выглядит:

Частые причины

1. Выход за границы массива. Индекс n у массива из n элементов, отрицательный индекс, i <= n вместо i < n в цикле. C индексы не проверяет. Небольшой выход часто не роняет программу, а молча портит соседние переменные; большой — роняет.

2. scanf без &. scanf("%d", n) вместо scanf("%d", &n): число записывается по «адресу», равному мусору в n. С ключом -Wall gcc предупреждает: format '%d' expects argument of type 'int *', but argument 2 has type 'int'.

3. Не проверен результат fopen или malloc. Файл не нашёлся — fopen вернул NULL, и первое же fscanf падает. Особенно часто это бывает, когда файл лежит не в той папке, из которой запускается программа. Проверяйте:

C
#include <stdio.h>

int main(void) {
    FILE *f = fopen("input.txt", "r");
    if (f == NULL) {
        perror("input.txt");
        return 1;
    }
    int x;
    if (fscanf(f, "%d", &x) == 1) {
        printf("%d\n", x);
    }
    fclose(f);
    return 0;
}

Если файла нет, программа напишет input.txt: No such file or directory и завершится, а не упадёт.

4. Изменение строки-литерала.

C
#include <stdio.h>

int main(void) {
    char *s = "hello";
    s[0] = 'H';
    printf("%s\n", s);
    return 0;
}

Результат не определён: строка в кавычках обычно лежит в памяти только для чтения, и в Linux программа падает на s[0] = 'H'. Чтобы строку можно было менять, объявите массив: char s[] = "hello";.

5. Указатель без значения или после free. int *p; *p = 1; — запись по случайному адресу. После free(p) память больше не принадлежит программе, обращение к ней — ошибка, даже если иногда «работает».

6. Большой массив внутри функции. Локальные переменные лежат в стеке: обычно 8 МБ в Linux и 1 МБ в Windows. int a[5000000] (20 МБ) внутри main его переполняет — программа падает сразу при запуске. Объявите массив вне функций (глобальные переменные лежат не в стеке) или выделите malloc.

7. Слишком глубокая рекурсия. Нет базы рекурсии или она не достигается — стек кончается.

Как найти место ошибки

Санитайзер адресов. В Linux и macOS соберите программу так:

Bash
gcc -g -fsanitize=address,undefined prog.c -o prog
./prog

При выходе за границы массива, обращении к освобождённой памяти и многих других ошибках программа остановится и напишет, что случилось и в какой строке. Ключ -g добавляет в программу номера строк. В MinGW для Windows санитайзер обычно не работает; в Visual Studio есть свой — ключ /fsanitize=address.

Отладчик gdb (программа, которая запускает вашу и показывает, где она остановилась). Соберите с -g, запустите gdb ./prog, наберите run, а после падения — bt: появится цепочка вызовов с номером строки, где программа упала. Выход из gdb — quit.

valgrind (Linux): valgrind ./prog показывает чтение и запись за пределами выделенной памяти и утечки. Ставится командой sudo apt install valgrind.

Вывод по шагам. Самый простой способ: выводите промежуточные значения в stderr (поток для сообщений об ошибках) — fprintf(stderr, "i = %d\n", i); — и смотрите, после какого вывода программа упала. В отличие от printf, вывод в stderr не задерживается в буфере и не пропадает при падении.

Если программа падает не каждый раз или по-разному на разных компьютерах, это почти всегда выход за границы массива или переменная без начального значения. Санитайзер находит такие ошибки быстрее всего.