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

/* 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(/* скалярное выражение */);

Когда скалярное выражение ложно, макрос 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 = %d\n", index);

40

41  return 0;

42 }

После компиляции и запуска оператор проверки в строке 12 «выстреливает»:

$ ch12-assert /* Запуск программы */

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() расширяется в код, который ничего не делает. Например:

$ gcc -DNDEBUG=1 ch12-assert.c -о ch12-assert /* Компиляция с -DNDEBUG */

$ ch12-assert /* Запуск */

Segmentation fault (core dumped) /* Что случилось? */

Здесь мы получили настоящий дамп ядра! Мы знаем, что операторы проверки были запрещены; сообщения «failed assertion» нет. Что же случилось? Рассмотрите строку 15 lsearch() при вызове из строки 37 main(). В этом случае переменная array равна NULL. Доступ к памяти через указатель NULL является ошибкой. (Технически различные стандарты оставляют «неопределенным» то, что происходит при разыменовывании указателя NULL. Наиболее современные системы делают то же, что и GNU/Linux; они завершают процесс, посылая ему сигнал SIGSEGV; это, в свою очередь, создает дамп ядра. Этот процесс описан в главе 10 «Сигналы».

Этот случай поднимает важный момент относительно операторов проверки. Часто программисты ошибочно используют операторы проверки вместо проверки ошибок времени исполнения. В нашем случае тест 'array != NULL' должен был быть проверкой времени исполнения:

if (array == NULL) return -1;

Тест 'size > 0' (строка 13) менее проблематичен; если size равен 0 или меньше 0, цикл никогда не исполнится, и lsearch() (правильно) возвратит -1. (По правде, этот оператор проверки не нужен, поскольку код правильно обрабатывает случай 'size <= 0'.)

Логика, стоящая за отменой оператора проверки, заключается в том, что дополнительные проверки могут снизить производительность программы и поэтому должны быть запрещены в заключительной версии программы. Хоар[123], однако, сделал такое замечание:

«В конце концов, абсурдно делать тщательные проверки безопасности при отладочных запусках, когда к результатам нет никакого доверия, а затем удалять их из финальных версий, когда ошибочный результат может быть дорогим или катастрофическим. Что бы мы подумали об энтузиасте-мореплавателе, который надевает свой спасательный жилет при тренировке на сухой земле и снимает его, как только выходит в море?»

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

Наконец, отметим следующее из раздела «Ошибки» справочной страницы GNU/Linux assert(3):

assert() реализован как макрос: если у проверяемого выражения есть побочные результаты, поведение программы может меняться в зависимости от того, определен ли NDEBUG. Это может создавать гейзенберговские ошибки, которые исчезают при отключении режима отладки.

вернуться

122

Как упоминалось в разделе 10.2 «Действия сигналов», некоторые дистрибутивы GNU/Linux запрещают создание файлов core. Чтобы снова разрешить их, поместите в свой файл ~/.profile строку 'ulimit -S -с unlimited' — Примеч. автора.

вернуться

123

Hints On Programming Language Design, C.A.R. Hoare Stanford University Computer Science Technical Report CS-73-403 (ftp://reports.stanford.edu/pub/cstr/reports/cs/tr/73/403/CS-TR-73-403.pdf). December, 1973 — Примеч. автора.