Wiki

Нелокальные Выходы

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


20.1 Введение в нелокальные Выходы


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

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

Вы определяете куда возвращать управление при нелокальных выходах,
вызывая функцию setjmp. Эта функция сохраняет информацию
относительно среды выполнения, в которой появляется обращение к
setjmp в объекте типа jmp_buf. После обращения к setjmp выполнение
программы продолжается как обычно, но если позже вызывается
longjmp с соответствующим объектом jmp_buf, управление передается
обратно в то место, где вызывалась setjmp. Возвращаемое значение из
setjmp используется, чтобы отличить обычный возврат и возврат,
сделанный обращением к longjmp, так что обращения к setjmp обычно
появляются в ` if '.

Вот пример программы, описанный выше:


                 #include <setjmp.h>
                 #include <stdlib.h>
                 #include <stdio.h>
                 jmp_buf main_loop;
                 void
                 abort_to_main_loop (int status)
                 {
                         longjmp (main_loop, status);
                 }
                 int
                 main (void)
                 {
                         while (1)
                                 if (setjmp (main_loop))
                                         puts ("Back at main loop....");
                                 else
                                         do_command ();
                 }
 
                 void
                         do_command (void)
                         {
                                 char buffer[128];
                                 if (fgets (buffer, 128, stdin) == NULL)
                                         abort_to_main_loop (-1);
                                 else
                                         exit (EXIT_SUCCESS);
                         }

Функция abort_to_main_loop вызывает непосредственную передачу
управления в main программы, независимо от того, где она
вызывается.

Способ управления внутри функции main может показаться сначала немного
таинственным, но это - фактически общая идиома для setjmp.
Нормальное обращение к setjmp возвращает нуль, так что "else"-часть
условного выражения выполнена. Если abort_to_main_loop вызывается
где-нибудь внутри выполнения команды do, то это фактически
действует как будто обращение к setjmp в main возвращалось со
значением -1.

Так, общий шаблон для использования setjmp выглядит вроде:


                         if (setjmp (buffer))
                                 /* Код, для выполнения после
          преждевременного возврата. */
                                 . . .
                         else
                                 /* Код, который будет
         выполнен после обычной установки
         возвращающей отметки. */
                                 . . .


20.2 Подробности нелокальных Выходов


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

Эти средства объявлены в " setjmp.h ".


       jmp_buf  (тип данных)

Объекты типа jmp_buf содержат информацию о состоянии, которое
будет восстановлено при нелокальном выходе.

Содержимое jmp_buf идентифицирует конкретное место
возвращения.



       int setjmp (jmp_buf state)  (макрос)


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

       void longjmp (jmp_buf state, int value) 

Эта функция восстанавливает текущее выполнение в состояние,
сохраненное в state, и продолжает выполнение от обращения к setjmp.
Возвращение из setjmp посредством longjmp возвращает значение
аргумента, который был передан к longjmp, а не 0. (Но если значение
задано как 0, setjmp возвращает 1).

Имеется множество неизвестных, но важных ограничений на
использование setjmp и longjmp. Большинство этих ограничений
присутствует, потому что нелокальные выходы требуют некоторых
волшебных свойств от части компилятора Cи и могут взаимодействовать
с другими частями языка странными способами.
setjmp - фактически макрокоманда без определения функции, так что
Вы не должны пробовать к " #undef " ее или брать адрес. Кроме того,
обращения к setjmp безопасны в только следующих контекстах:


  • Как тестовое выражение в операторе выбора или цикла (типа "if"
    или "while").

  • Как один операнд равенства или сравнения, которое появляется
    как тестовое выражение оператора выбора или цикла. Другой операнд
    должен быть целочисленным постоянным выражением.

  • Как операнд унарного оператора "!", который появляется как как
    тестовое выражение оператора выбора или цикла.

  • Как выражение утверждения.

Пункты возврата, допустимы только в течение динамической
протяженности функции которая вызвала setjmp, установить их. Если
Вы используете longjmp чтобы возвратиться отметке, которая была
установлена в функции, которая уже возвратилась, могут случиться
непредсказуемые и бедственные вещи.

Вы должны использовать в longjmp аргумент отличный от нуля. То
что longjmp отказывается передавать обратно аргумент нуля как
возвращаемое значение из setjmp, предназначено для
безопасности при случайном неправильном употреблении и не
является хорошим стилем программирования.


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


20.3 Нелокальные Выходы и Сигналы


В системах UNIX BSD, setjmp и longjmp также сохраняют и
восстанавливают набор блокированных сигналов; см. Раздел 21.7
[Блокирование Сигналов]. Однако, POSIX.1 стандарт требует чтобы
setjmp и longjmp не изменяли набор блокированных сигналов, и
обеспечивает дополнительную пару функций (sigsetjmp и sigsetjmp)
чтобы получить поведение BSD функций.

Поведение setjmp и longjmp в библиотеке GNU управляется
макрокомандами теста возможностей; см. Раздел 1.3.4 [Макрокоманды
Проверки Возможностей]. Значение по умолчанию в системе GNU ­
POSIX.1 поведение, а не поведение BSD.

Средства в этом разделе объявлены в заглавном файле " setjmp.h ".


       sigjmp_buf  (тип данных)

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


       int sigsetjmp (sigjmp_buf state, int savesigs)  (функция)

Подобна setjmp. Если savesigs отличен от нуля, набор
блокированных сигналов сохранен в state и будет восстановлен, если
siglongjmp позже будет выполнена с этим state.


       void siglongjmp (sigjmp_buf state, int value)  (функция)

Подобна longjmp кроме типа аргумента state. Если обращение к
sigsetjmp, которое устанавило это состояние, использовало savesigs
флаг отличный от нуля, siglongjmp также восстанавливает набор
блокированных сигналов.