The website "dmilvdv.narod.ru." is not registered with uCoz.
If you are absolutely sure your website must be here,
please contact our Support Team.
If you were searching for something on the Internet and ended up here, try again:

About uCoz web-service

Community

Legal information

13.1 Стратегия

13.1 Стратегия

Предыдущая  Содержание  Следующая V*D*V

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

 

OOC_Strategy_1

 

Во-первых, устанавливается обработчик ошибок. Либо опасное действие завершается правильно, либо обработчик ошибок получает шанс на очистку до завершения комбинированного действия. Чтобы реализовать эту схему восстановления после ошибки, в 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:

 

OOC_Strategy_2

 

В этом случае для восстановления необходимо два 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() использует самую верхнюю запись. Конечно, обработчик ошибок должен быть вытолкнут из стека до завершения соответствующей функции.

 

Предыдущая  Содержание  Следующая