Linux программирование в примерах - Роббинс Арнольд
Шрифт:
Интервал:
Закладка:
Операторы проверки особенно полезны для описания двух разновидностей инвариантов: предусловий и постусловий: условий, которые должны быть истинными соответственно перед и после исполнения сегмента кода. Простым примером предусловий и постусловий является линейный поиск:
/* lsearch --- возвратить индекс с данным значением в массиве или -1,
если не найдено */
int lsearch(int *array, size_t size, int value) {
size_t i;
/* предусловие: array != NULL */
/* предусловие: size > 0 */
for (i = 0; i < size; i++)
if (array[i] == value)
return i;
/* постусловие: i == size */
return -1;
}
Этот пример определяет условия, используя комментарии. Но не было бы лучше проверить условия с использованием кода? Это является задачей макроса assert():
#include <assert.h> /* ISO С */
void assert(/* <i>скалярное выражение</i> */);
Когда скалярное выражение ложно, макрос assert() выводит диагностическое сообщение и завершает программу (с помощью функции abort(); см. раздел 12.4 «Совершение самоубийства: abort()»). ch12-assert.c снова предоставляет функцию lsearch(), на этот раз с оператором проверки и функцией main():
1 /* ch12-assert.с --- демонстрация операторов проверки */
2
3 #include <stdio.h>
4 #include <assert.h>
5
6 /* lsearch --- возвращает индекс с данным значением в массиве или -1, если не найдено */
7
8 int lsearch(int *array, size_t size, int value)
9 {
10 size_t i;
11
12 assert(array != NULL);
13 assert(size > 0);
14 for (i = 0; i < size; i++)
15 if (array[i] == value)
16 return i;
17
18 assert(i == size);
19
20 return -1;
21 }
22
23 /* main --- проверить наши условия */
24
25 int main(void)
26 {
27 #define NELEMS 4
28 static int array[NELEMS] = { 1, 17, 42, 91 };
29 int index;
30
31 index = lsearch(array, NELEMS, 21);
32 assert(index == -1);
33
34 index = lsearch(array, NELEMS, 17);
35 assert(index == 1);
36
37 index = lsearch(NULL, NELEMS, 10); /* won't return */
38
39 printf("index = %dn", index);
40
41 return 0;
42 }
После компиляции и запуска оператор проверки в строке 12 «выстреливает»:
$ <b>ch12-assert</b> /* Запуск программы */
ch12-assert: ch12-assert.c:12: lsearch: Assertion 'array != ((void *)0)' failed.
Aborted (core dumped)
Сообщение от assert() варьирует от системы к системе. Для GLIBC на GNU/Linux сообщение включает имя программы, имя файла с исходным кодом и номер строки, имя функции, а затем текст завершившегося неудачей условия. (В этом случае именованная константа NULL проявляется в виде своего макрорасширения '((void*)0)'.)
Сообщение 'Aborted (core dumped)' означает, что ch12-assert создала файл core; т.е. снимок адресного пространства процесса непосредственно перед его завершением.[122] Этот файл может быть использован впоследствии с отладчиком; см. раздел 15.3 «Основы GDB». Создание файла core является намеренным побочным результатом assert(); предполагается, что произошла решительная ошибка, и вы хотите исследовать процесс с помощью отладчика для ее определения.
Вы можете отменить оператор проверки, компилируя свою программу с помощью опции командной строки '-DNDEBUG'. Когда этот макрос определен до включения <assert.h>, макрос assert() расширяется в код, который ничего не делает. Например:
$ <b>gcc -DNDEBUG=1 ch12-assert.c -о ch12-assert</b> /* Компиляция с -DNDEBUG */
$ <b>ch12-assert</b> /* Запуск */
Segmentation fault (core dumped) /* Что случилось? */
Здесь мы получили настоящий дамп ядра! Мы знаем, что операторы проверки были запрещены; сообщения «failed assertion» нет. Что же случилось? Рассмотрите строку 15 lsearch() при вызове из строки 37 main(). В этом случае переменная array равна NULL. Доступ к памяти через указатель NULL является ошибкой. (Технически различные стандарты оставляют «неопределенным» то, что происходит при разыменовывании указателя NULL. Наиболее современные системы делают то же, что и GNU/Linux; они завершают процесс, посылая ему сигнал SIGSEGV; это, в свою очередь, создает дамп ядра. Этот процесс описан в главе 10 «Сигналы».