Выбрать главу

Дадим несколько полезных советов.

■ Проверяйте наличие пустых указателей, например в списке аргументов функции. Сообщение об ошибке, генерируемое строкой {assert (pointer != NULL)},

Assertion 'pointer != ((void *)0)' failed.

более информативно, чем сообщение, выдаваемое в ответ на попытку раскрытия пустого указателя:

Segmentation fault (core dumped)

■ Проверяйте значения параметров функции. Например, если в функции предполагается, что параметр foo имеет только положительные значения, поставьте следующую проверку в самом начале тела функции:

assert(foo > 0);

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

2.2.2. Ошибки системных вызовов

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

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

Сбои системных вызовов происходят в самых разных ситуациях.

■ В системе могут закончиться ресурсы (или же программа может исчерпать лимит ресурсов, наложенный на нее системой). Например, программа может запросить слишком много памяти, записать чересчур большой объем данных на диск или открыть чрезмерное количество файлов одновременно.

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

■ Аргументы системного вызова могут оказаться неправильными либо по причине ошибочно введенных пользователем данных, либо из-за ошибки самой программы. Например, программа может передать системному вызову неправильный адрес памяти или неверный дескриптор файла. Другой вариант ошибки — попытка открыть каталог вместо обычного файла или передать имя файла системному вызову, ожидающему имя каталога.

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

■ Выполнение системного вызова иногда прерывается внешними событиями, к каковым относится, например, получение сигнала. Это не обязательно означает ошибку, но ответственность за перезапуск системного вызова возлагается на программу.

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

2.2.3. Коды ошибок системных вызовов

Большинство системных вызовов возвращает 0, если операция выполнена успешно, и ненулевое значение — в случае сбоя. (В некоторых случаях используются другие соглашения. Например, функция malloc() при возникновении ошибки возвращает нулевой указатель. Никогда не помешает прочесть man-страницу, посвященную требуемому системному вызову.) Обычно этой информации достаточно для того, чтобы решить, следует ли продолжать привычное выполнение программы. Но для более специализированной обработки ошибок необходимы дополнительные сведения.

Практически все системные вызовы сохраняют в специальной переменной errno расширенную информацию о произошедшей ошибке.[7] В эту переменную записывается число, идентифицирующее возникшую ситуацию. Поскольку все системные вызовы работают с одной и той же переменной, необходимо сразу же после завершения функции скопировать значение переменной в другое место. Переменная errno модифицируется после каждого системного вызова.

вернуться

7

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