ПрограммированиеBackend C разработчик

Как реализовать обработку ошибок при работе с функциями стандартной библиотеки C? В чем специфика использования errno, когда лучше использовать коды возврата и как не допустить ошибок при обработке?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса

Обработка ошибок в языке C всегда была задачей разработчика. В стандартной библиотеке нет исключений, ошибки возвращаются через коды возврата или глобальную переменную errno (начало использования — UNIX 1970-х годов, далее POSIX и ANSI C). Такие механизмы используются и сегодня для управления потоком выполнения при нештатных ситуациях.

Проблема

Ошибки при работе со стандартными функциями (файловые операции, выделение памяти, строковые функции) могут быть незаметны без особого контроля. Неправильная обработка — игнорирование кода возврата, неправильная интерпретация errno, отсутствие очистки ресурсов — приводит к неверной работе программы, падениям и уязвимостям.

Решение

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

Пример кода:

#include <stdio.h> #include <errno.h> #include <string.h> FILE *open_file(const char *filename) { errno = 0; FILE *f = fopen(filename, "r"); if (!f) { fprintf(stderr, "Ошибка: %s ", strerror(errno)); } return f; }

Ключевые особенности:

  • errno используется только для диагностики ошибок, возникших в системных вызовах/стандартных функциях.
  • Внутри функций удобно возвращать int-коды ошибок, ноль — успех, отрицательное число или специальные значения — сбой.
  • Важно не полагаться на значение errno, если перед этим не было ошибки.

Вопросы с подвохом.

Можно ли использовать errno для пользовательских функций, если хочется передавать ошибки наверх?

Нет, errno предназначена только для стандартных библиотечных и системных вызовов. Она глобальна, может быть перезаписана в любой точке и не подходит для своих функций.

Обязательно ли устанавливать errno перед каждым вызовом функции?

Нет, но рекомендуется сбрасывать errno (например, к нулю) перед вызовами, если планируется анализ изменений. Не каждая функция изменяет errno при успехе, а только при ошибке.

errno = 0; ... вызов опасной функции ...

Можно ли доверять errno после любой функции?

Только для тех функций, которые согласно стандарту явно устанавливают её при неудаче. Многие функции стандартной библиотеки не трогают errno при успехе. Документация — ваш друг.

Типовые ошибки и анти-паттерны

  • Игнорирование кода возврата у функций (например, fopen, malloc, write).
  • Перезапись errno до использования её значения.
  • Использование errno для своих собственных ошибок в проекте.

Пример из жизни

Негативный кейс

Открытие файла без проверки результата, ошибки не анализируются, программа работает неверно при отсутствии файла:

Плюсы:

  • Меньше кода, быстро работает при простых сценариях.

Минусы:

  • Криптичные сбои при ошибках, невозможность диагностики.

Позитивный кейс

После каждой критичной функции результат проверяется, в случае ошибки выводится подробное сообщение с strerror(errno), исполнение корректно завершается:

Плюсы:

  • Программу легко поддерживать и отлаживать, высокая стабильность.

Минусы:

  • Немного больше кода, чуть выше сложность.