Арнольд Роббинс - Linux программирование в примерах
Многие системы предоставляют также другие значения ошибок, а в более старых системах может не быть всех перечисленных значений ошибок. Полный список следует проверить с помощью справочных страниц 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: %sn",
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: %sn",
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 становились все более коммерческими, использование этих идентификаторов постепенно уменьшалось, поскольку знание положения в исходном коде дает немного пользы, когда имеется лишь двоичный исполняемый файл.
Сегодня, хотя системы GNU/Linux поставляются с исходными кодами, указанный исходный код часто не устанавливается по умолчанию. Поэтому использование этих идентификаторов для сообщений об ошибках не представляет дополнительной ценности. GNU Coding Standards даже не упоминает их.
4.4. Ввод и вывод
Все операции Linux по вводу/выводу осуществляются посредством дескрипторов файлов. Данный раздел знакомит с дескрипторами файлов, описывает, как их получать и освобождать, и объясняет, как выполнять с их помощью ввод/вывод.
4.4.1. Понятие о дескрипторах файлов
Дескриптор файла является целым значением. Действительные дескрипторы файлов начинаются с 0 и растут до некоторого установленного системой предела. Эти целые фактически являются индексами таблицы открытых файлов для каждого процесса (Таблица поддерживается внутри операционной системы; она недоступна запущенным программам.) В большинстве современных систем размеры таблиц большие. Команда 'ulimit -n' печатает это значение:
$ ulimit -n
1024
Из С максимальное число открытых файлов возвращается функцией getdtablesize() (получить размер таблицы дескрипторов):
#include <unistd.h> /* Обычный */
int getdtablesize(void);
Следующая небольшая программа выводит результат работы этой функции:
/* ch04-maxfds.с --- Демонстрация getdtablesize(). */
#include <stdio.h> /* для fprintf(), stderr, BUFSIZ */
#include <unistd.h> /* для ssize_t */
int main(int argc, char **argv) {
printf("max fds: %dn", getdtablesize());
exit(0);
}
Неудивительно, что после компиляции и запуска эта программа выводит то же значение, что и ulimit:
$ ch04-maxfds
max fds: 1024
Дескрипторы файлов содержатся в обычных переменных int; для использования с системными вызовами ввода/вывода можно увидеть типичные объявления вида 'int fd'. Для дескрипторов файлов нет предопределенного типа.
В обычном случае каждая программа начинает свою работу с тремя уже открытыми для нее дескрипторами файлов. Это стандартный ввод, стандартный вывод и стандартная ошибка, с дескрипторами файлов 0, 1 и 2 соответственно. (Если не было использовано перенаправление, каждый из них связан с клавиатурой и с экраном.)
Очевидные символические константы. Оксюморон?При работе с системными вызовами на основе дескрипторов файлов и стандартных ввода, вывода и ошибки целые константы 0, 1 и 2 обычно используются прямо в коде. В подавляющем большинстве случаев использование таких символических констант (manifest constants) является плохой мыслью. Вы никогда не знаете, каково значение некоторой случайной целой константы и имеет ли к ней какое-нибудь отношение константа с тем же значением, использованная в другой части кода. С этой целью стандарт POSIX требует объявить следующие именованные константы (symbolic constants) в <unistd.h>:
STDIN_FILENO «Номер файла» для стандартного ввода: 0.
STDOUT_FILENO Номер файла для стандартного вывода: 1.
STDERR_FILENO Номер файла для стандартной ошибки: 2.
Однако, по нашему скромному мнению, использование этих макросов избыточно. Во-первых, неприятно набирать 12 или 13 символов вместо 1. Во-вторых, использование 0, 1 и 2 так стандартно и так хорошо известно, что на самом деле нет никаких оснований для путаницы в смысле этих конкретных символических констант.
С другой стороны, использование этих констант не оставляет сомнений в намерениях программиста. Сравните это утверждение:
int fd = 0;
Инициализируется ли fd значением стандартного ввода, или же программист благоразумно инициализирует свои переменные подходящим значением? Вы не можете этого сказать.
Один из подходов (рекомендованный Джеффом Колье (Geoff Collyer)) заключается в использовании следующего определения enum:
enum { Stdin, Stdout, Stderr };
Затем эти константы можно использовать вместо 0, 1 и 2. Их легко читать и печатать.
4.4.2. Открытие и закрытие файлов
Новые дескрипторы файлов получают (наряду с другими источниками) в результате системного вызова open(). Этот системный вызов открывает файл для чтения или записи и возвращает новый дескриптор файла для последующих операций с этим файлом. Мы видели объявление раньше:
#include <sys/types.h> /* POSIX */