Обработка ошибок в языке Си

Обработка ошибок в языке Си

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

квадратные уравнения

Оказывается, этот подход очень распространен в библиотечном коде проекта GNU. Например, если мы посмотрим на документацию функции `fopen(...)`

документацию функции

то есть, функция `fopen()` вернет:

  • Указатель типа `FILE*`, если программа отработала верно 
  • `NULL`, если во время выполнения возникла ошибка (например, файл который мы пытаемся открыть, не существует). Часть про `errno` пока что игнорируем.

А это значит, что обработка ошибок функции fopen должна происходить примерно вот так:

обработка ошибок функции fopen

Файл `blabla.txt` я специально не создал, а значит, моя программа ожидаемо закончится с сообщением `something went wrong with fopen()!`. Но бывают (почти всегда) ситуации, в которых об ошибке хочется знать чуть больше, чем просто то что она произошла. Тут и вступает в дело переменная `errno`, которая глобальная и определена во всех больших хедерах (в том числе в `stdlib.h` и `stdio.h`, но не в `string.h` - подумайте, почему). Где-то в недрах `errno.h` есть специальный массив строк под названием `sys_errlist`, к которому у вас, как у программиста, строго говоря, нет доступа. Строки в этом массиве - сообщения об ошибках, а `errno`, которая выставилась в результате ошибки - индекс в этом массиве, который указывает на описание ошибки, которая возникла. Существуют специальные функции, которые помогают вам выводить ошибки, например `perror()`. Перепишем наш код с обработкой ошибок через `perror()`

специальные функции

Теперь при ошибке, при открытии файла, вызовется функция `perror()`, она проверит чему равна переменная `errno`(которая, повторюсь, глобальная и хитро определена в заголовке как `stdio.h`, так и `errno.h`), и она найдет нужное сообщение об ошибке, то есть `main: No such file or directory`. В данном случае, я посчитал, что ошибка критическая, поскольку любое продолжение программы приведет к Segmentation fault, поэтому после вывода сообщения об ошибке, программа завершается с кодом errno (что, в принципе, необязательно). Узнать что делает аргумент функции `perror()` остается заданием для читателя. Узнать это можно в man-страницах ;)

Более лаконично этот код записывается так:

код записывается так

а ЕЩЕ более лаконично

более лаконичный код

(Это решение плохо тем, что иногда в случае ошибки функции возвращают `NULL`, а иногда `-1` в случае ошибок, такие штуки ВСЕГДА надо проверять в man-страницах.)

Важные замечания:

  • НЕ стоит проверять значение `errno` в случае успешного выполнения функции, потому что оно, строго говоря, не определено в этом случае (хотя, в большинстве случаев оно будет значения `Success`)
  • Если у вас несколько функций подряд, которые используют errno, то проверять наличие ошибки нужно после КАЖДОЙ из них.

проверка на наличие ошибок кода

Есть ли альтернативы? Ну, пока не придумали. Даже язык Go, по идеологии внук языка Cи, страдает от постоянных

язык Go страдает от ошибок

А этот язык сейчас один из самых популярных! (Хотя в Go2 придумали безумно интересный механизм обработки ошибок, кардинально отличающийся от того, что написано здесь и других прочих объектно-ориентированных exception-handling).

Существует механизм `SJLJ`, пользоваться которым без надобности не рекомендуется.

Берегите себя и пишите безопасный код ;)