Многие системы предоставляют также другие значения ошибок, а в более старых системах может не быть всех перечисленных значений ошибок. Полный список следует проверить с помощью справочных страниц intro(2) и errno(2) для локальной системы.
ЗАМЕЧАНИЕ. errno
следует проверять лишь после того, как возникла ошибка и до того, как сделаны дальнейшие системные вызовы. Начальное значение той переменной 0. Однако, в промежутках между ошибками ничто не изменяет ее значения, это означает, что успешный системный вызов не восстанавливает значение 0. Конечно, вы можете вручную установить ее в 0 в самом начале или когда захотите, однако это делается редко.
Сначала мы используем errno
лишь для сообщений об ошибках. Для этого имеются две полезные функции. Первая — perror()
:
#include <stdio.h> /* ISO С */
void perror(const char *s);
Функция perror()
выводит предоставленную программой строку, за которой следует двоеточие, а затем строка, описывающая значение errno
:
if (some_system_call(param1, param2) < 0) {
perror("system call failed");
return 1;
}
Мы предпочитаем функцию strerror()
, которая принимает параметр со значением ошибки и возвращает указатель на строку с описанием ошибки:
#include <string.h> /* ISO С */
char *strerror(int errnum);
strerror()
предоставляет для сообщений об ошибках максимальную гибкость, поскольку fprintf()
дает возможность выводить ошибки любым нужным нам способом, наподобие этого.
if (some_system_call(param1, param2) < 0) {
fprintf(stderr, "%s: %d, %d: some_system_call failed: %s\n",
argv[0], param1, param2, strerror(errno));
return 1;
}
По всей книге вы увидите множество примеров использования обеих функций.
4.3.2. Стиль сообщения об ошибках
Для использования в сообщениях об ошибках С предоставляет несколько специальных макросов. Наиболее широкоупотребительными являются __FILE__
и __LINE__
, которые разворачиваются в имя исходного файла и номер текущей строки в этом файле. В С они были доступны с самого начала. C99 определяет дополнительный предопределенный идентификатор, __func__
, который представляет имя текущей функции в виде символьной строки. Макросы используются следующим образом:
if (some_system_call(param1, param2) < 0) {
fprintf(stderr, "%s: %s (%s %d): some_system_call(%d, %d) failed: %s\n",
argv[0], __func__, __FILE__, __LINE__,
param1, param2, strerror(errno));
return 1;
}
Здесь сообщение об ошибке включает не только имя программы, но также и имя функции, имя исходного файла и номер строки. Полный список идентификаторов, полезных для диагностики, приведен в табл. 4.2.
Таблица 4.2. Диагностические идентификаторы C99
Идентификатор | Версия С | Значение |
---|---|---|
__DATE__ |
C89 | Дата компиляции в виде «Mmm nn yyyy » |
__FILE_ |
Оригинальная | Имя исходного файла в виде «program.c » |
__LINE__ |
Оригинальная | Номер строки исходного файла в виде 42 |
__TIME__ |
C89 | Время компиляции в виде «hh:mm:ss » |
__func__ |
C99 | Имя текущей функции, как если бы было объявлено const char __func__[] = "name" |
Использование __FILE__
и __LINE__
было вполне обычно для ранних дней Unix, когда у большинства людей были исходные коды и они могли находить ошибки и устранять их. По мере того, как системы Unix становились все более коммерческими, использование этих идентификаторов постепенно уменьшалось, поскольку знание положения в исходном коде дает немного пользы, когда имеется лишь двоичный исполняемый файл.