13.1 Стратегия |
Предыдущая Содержание Следующая |
Если наш калькулятор испытывает затруднения при загрузке определения функции, мы сталкиваемся с типичной проблемой восстановления после ошибки: открытый поток должен быть закрыт до того, как можно будет сделать вызов error(), чтобы сообщить об ошибке и вернуться к основному циклу. Следующая картинка показывает, что простое опасное действие должно быть обёрнуто в какой-нибудь логическую схему обработки ошибок:
Во-первых, устанавливается обработчик ошибок. Либо опасное действие завершается правильно, либо обработчик ошибок получает шанс на очистку до завершения комбинированного действия. Чтобы реализовать эту схему восстановления после ошибки, в ANSI-C используются setjmp() и longjmp():
#include <setjmp.h>
static jmp_buf onError;
static void cause() { longjmp(onError, 1); }
action () { if (! setjmp(onError)) опасное действие else обработчик ошибки }
setjmp() инициализирует onError и возвращает ноль. Если в опасном действии или в функции, вызванной оттуда, что-то идёт не так, мы сигнализируем об ошибке, вызывая cause(). longjmp() в этой функции использует информацию в onError, чтобы выполнить вторичный возврат от вызова до setjmp(), который инициализировал onError. Вторичный возврат поставляет второй параметр longjmp() в виде значения функции; если это значение равно нулю, возвращается единица. Всё идёт ужасно неправильно, если функция, которая вызвала setjmp(), больше не активна. В терминологии показанной выше картинки ошибка отсылает к вызову setjmp(), чтобы внести информацию для обработки ошибки. опасное действие выполняется, если setjmp() возвращает ноль; или в противном случае выполняется обработчик ошибок. cause() вызывают, чтобы инициировать восстановление после ошибки, передавая управление обработчику ошибок. Мы использовали эту простую модель, чтобы восстановиться после синтаксических ошибок глубоко внутри рекурсивного спуска в нашем калькуляторе. Всё становится более запутанным, если обработчики ошибок должны быть вложены. Вот что происходит в калькуляторе во время работы load:
В этом случае для восстановления необходимо два longjmp() : onError возвращает к основному циклу, а onLoadError используется для очистки после проблемы при операции загрузки:
jmp_buf onError, onLoadError;
#define cause(x) longjmp(x, 1)
mainLoop () { if (! setjmp(onError)) loadFile(); else какая-то проблема }
loadFile () { if (! setjmp(onLoadError)) работаем с файлом else закрываем файл cause(onError); }
Набросок кода показывает, что cause() так или иначе должна знать, как далеко должно пойти восстановление. Мы можем использовать с этой целью какой-нибудь аргумент или скрытую глобальную структуру. Если мы даём cause() явный параметр, вероятно, он должен ссылаться на какой-то глобальный символ, чтобы его можно было вызывать из других файлов. Очевидно, чтоб глобальный символ не должен использоваться в момент ошибки. У этого есть дополнительный недостаток, заключающийся в том, что это часть клиентского кода, то есть хотя такой символ значим только для определённого обработчика ошибок, он записан в коде, защищённом обработчиком. Если этот код вызывается из какого-то другого места, мы должны быть уверены, что это случайно не относится к неактивной точке восстановления. Намного лучшая стратегия — стек значений jmp_buf. Какая-то функция устанавливает обработчик ошибок, продвигая этот стек, а cause() использует самую верхнюю запись. Конечно, обработчик ошибок должен быть вытолкнут из стека до завершения соответствующей функции.
|
Предыдущая Содержание Следующая |