Чтение онлайн

ЖАНРЫ

Программирование для Linux. Профессиональный подход

Самьюэл Алекс

Шрифт:

В представленные ниже фрагменты программ сознательно не включены громоздкие коды проверки ошибок и восстановления после них. так как это привело бы к потере наглядности при рассмотрении основных методик. Тем не менее мы вернемся к данной теме в главе 11, "Демонстрационное Linux-приложение", где будут приведены полностью работающие программы.

2.2.1. Макрос assert

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

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

Простейший способ выявления ненормальных ситуаций — стандартный макрос

assert
в языке С. Его аргументом является булево выражение. Программа завершается, если выражение оказывается ложным, при этом выводится сообщение об ошибке с указанием исходного файла и номера строки, а также текста выражения, приведшего к ошибке. Макрос
assert
оказывается очень полезным для самых разных проверок целостности, выполняемых внутри программы. Например, с помощью этого макроса проверяют правильность аргументов функций, выполнение входных и выходных условий при вызове функций (а также методов C++) и наличие непредвиденных возвращаемых значений.

Каждый вызов макроса

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

В программах, критических к требованиям производительности, проверки

assert
на этапе выполнения могут представлять собой слишком большую нагрузку. В таких случаях программа компилируется с установленной макроконстантой
NDEBUG
; для этого в командной строке компилятора указывается флаг
– DNDEBUG
. При наличии данной макроконстанты препроцессор убирает из тела программы все вызовы макроса
assert
. И все же помните: делать это имеет смысл только тогда, когда производительность является узким местом программы, причем макрос нужно отключать лишь в наиболее критических файлах.

В связи с тем что макрос

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

Предположим, к примеру, что в цикле вызывается функция

do_something
. В случае успешного выполнения она возвращает 0, иначе — ненулевое значение. Легкомысленный программист считает, что функция всегда завершается успешно, поэтому возникает соблазн написать так:

for (i =0; i < 100; ++i)

 assert(do_something == 0);

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

NDEBUG
. В результате из программы будут удалены все макросы
assert
, и функция
do_something
вообще не будет вызвана. На самом деле необходимо использовать следующий подход:

for (i = 0; i < 100; ++i) {

 int status = do_something;

 assert(status == 0);

}

Еще один важный

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

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

■ Проверяйте наличие пустых указателей, например в списке аргументов функции. Сообщение об ошибке, генерируемое строкой

{assert (pointer != NULL)}
,

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

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

Segmentation fault (core dumped)

■ Проверяйте значения параметров функции. Например, если в функции предполагается, что параметр

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

assert(foo > 0);

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

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

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

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

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

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

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

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

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

Поделиться с друзьями: