Setjmp.h - Setjmp.h

setjmp.h это заголовок определено в Стандартная библиотека C для обеспечения «нелокальных прыжков»: поток управления что отличается от обычного подпрограмма последовательность вызова и возврата. Дополнительные функции setjmp и longjmp обеспечить эту функциональность.

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

Функции-члены

интервал setjmp (jmp_buf env)Устанавливает местный jmp_buf буфер и инициализирует его для перехода. Эта рутина[1] сохраняет среду вызова программы в буфере среды, заданном параметром env аргумент для последующего использования longjmp. Если возврат от прямого вызова, setjmp возвращает 0. Если возврат от вызова longjmp, setjmp возвращает ненулевое значение.
void longjmp (jmp_buf env, значение int)Восстанавливает контекст буфера среды env который был спасен призывом setjmp рутина[1] в том же вызове программы. Вызов longjmp из вложенного обработчика сигналов неопределенный. Значение, указанное ценить передается из longjmp к setjmp. После longjmp завершена, выполнение программы продолжается, как если бы соответствующий вызов setjmp только что вернулся. Если ценить перешел к longjmp равно 0, setjmp будет вести себя так, как если бы он вернул 1; в противном случае он будет вести себя так, как если бы он вернулся ценить.

setjmp сохраняет текущую среду (состояние программы) в какой-то момент выполнения программы в структуру данных платформы (jmp_buf), который может быть использован на более позднем этапе выполнения программы longjmp восстановить состояние программы до сохраненного setjmp в jmp_buf. Этот процесс можно представить как «прыжок» назад к точке выполнения программы, где setjmp спасла окружающую среду. (Очевидный) возвращаемое значение из setjmp указывает, достигло ли управление этой точки нормально (ноль) или от вызова до longjmp (ненулевое). Это приводит к общему идиома: если( setjmp(Икс) ){/ * обрабатываем longjmp (x) * /}.

POSIX.1 не указывает, setjmp и longjmp сохранить и восстановить текущий набор заблокированных сигналы; если программа использует обработку сигналов, она должна использовать POSIX sigsetjmp/siglongjmp.

Типы участников

jmp_bufТип массива, например struct __jmp_buf_tag [1],[2] подходит для хранения информации, необходимой для восстановления среды вызова.

Обоснование C99 описывает jmp_buf как тип массива для Обратная совместимость; существующий код относится к jmp_buf места хранения по имени (без & адрес оператора), что возможно только для типов массивов.[3]

Предостережения и ограничения

Когда "нелокальный переход" выполняется через setjmp/longjmp в C ++, нормальный "разматывание стека "не происходит. Следовательно, никакие требуемые действия по очистке также не выполняются. Это может включать закрытие файловые дескрипторы, промывка буферы, или освобождение память, выделенная кучей.

Если функция, в которой setjmp был вызван возвратом, уже нельзя безопасно использовать longjmp с соответствующими jmp_buf объект. Это потому, что кадр стека становится недействительным, когда функция возвращается. Вызов longjmp восстанавливает указатель стека, который - поскольку функция вернула - будет указывать на несуществующий и потенциально перезаписанный или поврежденный фрейм стека.[4][5]

По аналогии, C99 не требует этого longjmp сохранить текущий кадр стека. Это означает, что переход к функции, которая была завершена вызовом longjmp не определено.[6] Однако большинство реализаций longjmp оставить фрейм стека нетронутым, позволяя setjmp и longjmp использоваться для переключения между двумя или более функциями - функция, используемая для многозадачность.

По сравнению с механизмами в языках программирования более высокого уровня, таких как Python, Ява, C ++, C #, и даже языки до C, такие как Алгол 60, техника использования setjmp/longjmp реализовать механизм исключения громоздко. Эти языки предоставляют более мощные Обработка исключений методы, в то время как языки, такие как Схема, Болтовня, и Haskell предоставить даже более общий продолжение -управляемые конструкции.

Пример использования

Простой пример

В приведенном ниже примере показана основная идея setjmp. Там, главный() звонки первый(), который, в свою очередь, вызывает второй(). Потом, второй() прыгает обратно в главный(), пропуская первый()зов printf ().

#включают <stdio.h>#включают <setjmp.h>статический jmp_buf бух;пустота второй() {    printf("второй п");         // печатает    longjmp(бух,1);             // возвращается туда, где был вызван setjmp - теперь setjmp возвращает 1}пустота первый() {    второй();    printf("первый п");          // не печатает}int главный() {       если (!setjmp(бух))        первый();                // при выполнении setjmp вернул 0    еще                        // когда longjmp перескакивает назад, setjmp возвращает 1        printf("главный п");       // печатает    возвращаться 0;}

При выполнении вышеуказанная программа выведет:

второй главный

Обратите внимание, что хотя первый() вызывается подпрограмма "первый"никогда не печатается".главный"печатается как условное выражение если (! setjmp (buf)) выполняется второй раз.

Обработка исключений

В этом примере setjmp используется для скобок обработки исключений, например пытаться на некоторых других языках. Призыв к longjmp аналогичен бросать оператор, позволяющий исключению возвращать статус ошибки непосредственно в setjmp. Следующий код соответствует Стандарт ISO 1999 г. и Единая спецификация UNIX ссылаясь на setjmp в ограниченном диапазоне контекстов:[7]

  • Как условие если, выключатель или оператор итерации
  • Как указано выше в сочетании с одиночным ! или сравнение с целочисленной константой
  • Как утверждение (с неиспользованным возвращаемым значением)

Следование этим правилам может упростить реализацию для создания буфера среды, что может быть деликатной операцией.[3] Более общее использование setjmp может вызвать неопределенное поведение, например повреждение локальных переменных; соответствующие компиляторы и среды не обязаны защищать или даже предупреждать о таком использовании. Однако несколько более сложные идиомы, такие как переключатель ((тип_исключения = setjmp (env))) {} распространены в литературе и на практике и остаются относительно портативными. Ниже представлена ​​простая соответствующая методология, в которой вместе с буфером состояний поддерживается дополнительная переменная. Эта переменная может быть преобразована в структуру, включающую сам буфер.

В более современном примере обычный блок «try» будет реализован как setjmp (с некоторым кодом подготовки для многоуровневых прыжков, как показано на первый), «throw» as longjmp с необязательным параметром в качестве исключения и «catch» в качестве блока «else» в разделе «try».

#включают <setjmp.h>#включают <stdio.h>#включают <stdlib.h>#включают <string.h>статический пустота первый();статический пустота второй();/ * Используйте статическую переменную с файловой областью для стека исключений, чтобы мы могли получить доступ * это где угодно в пределах этой единицы перевода. * /статический jmp_buf exception_env;статический int исключение_типа;int главный(пустота) {    char* летучий mem_buffer = НОЛЬ;    если (setjmp(exception_env)) {        // если мы попали сюда, произошло исключение        printf("первая ошибка, тип исключения:% d п", исключение_типа);    } еще {        // Запускаем код, который может сигнализировать об ошибке через longjmp.        ставит("сначала звоню");        первый();        mem_buffer = маллок(300); // выделяем ресурс        printf("% s п", strcpy(mem_buffer, "сначала удалось")); // не достиг    }    свободный(mem_buffer); // NULL можно передать в free, никаких операций не выполняется    возвращаться 0;}статический пустота первый() {    jmp_buf my_env;    ставит("входящий первым"); // достиг    memcpy(my_env, exception_env, размер my_env);    выключатель (setjmp(exception_env)) {        дело 3: // если мы попали сюда, произошло исключение.            ставит("второй сбой, тип исключения: 3; переназначение на тип 1");            исключение_типа = 1;        дефолт: // провалиться            memcpy(exception_env, my_env, размер exception_env); // восстанавливаем стек исключений            longjmp(exception_env, исключение_типа); // продолжаем обработку исключения        дело 0: // нормальная, желаемая операция            ставит("вызывающий второй"); // достиг             второй();            ставит("второй преуспел"); // не достиг    }    memcpy(exception_env, my_env, размер exception_env); // восстанавливаем стек исключений    ставит("уходящий первым"); // никогда не достигал}статический пустота второй() {    ставит("второй вход" ); // достиг    исключение_типа = 3;    longjmp(exception_env, исключение_типа); // объявляем, что программа потерпела неудачу    ставит("уходящий второй"); // не достиг}

Результат этой программы:

вызов первого вызова второй вызов второй секунды завершился неудачно, тип исключения: 3; переназначение на тип 1 сначала не удалось, тип исключения: 1

Хотя исключение_типа Переменная технически здесь не нужна, поскольку setjmp возвращает то ненулевое значение, с которым был вызван longjmp (как во втором и первом), на практике будет использоваться более сложный глобальный объект для размещения более богатых исключений.

В реальном мире setjmp-longjmp (sjlj) использовался как стандартный способ обработки исключений в сторонних компиляторах Windows C ++ (а именно MinGW ), так как родной Структурированная обработка исключений в целом плохо документирован, а также был запатентован для 32-битной Windows до 2014 года.

Кооперативная многозадачность

C99 предусматривает, что longjmp гарантированно работает только тогда, когда адресатом является вызывающая функция, т. е. что область назначения гарантированно не повреждена. Переход к функции, которая уже завершилась возвращаться или же longjmp не определено.[6] Однако большинство реализаций longjmp не уничтожайте специально локальные переменные при выполнении прыжка. Поскольку контекст сохраняется до тех пор, пока его локальные переменные не будут стерты, его можно восстановить с помощью setjmp. Во многих средах (например, Действительно простые темы и TinyTimbers ), идиомы, такие как если (! setjmp (child_env)) longjmp (caller_env); может позволить вызываемой функции эффективно приостановить и возобновить работу в setjmp.

Это используется библиотеками потоков для предоставления совместная многозадачность объекты без использования setcontext или другой волокно удобства. В то время как setcontext - это библиотечная служба, которая может создавать контекст выполнения в памяти, выделенной кучей, и может поддерживать другие службы, такие как защита от переполнения буфера,[нужна цитата ] злоупотребление setjmp реализуется программистом, который может зарезервировать память в стеке и не уведомить библиотеку или операционную систему о новом рабочем контексте. С другой стороны, реализация библиотеки setcontext может использовать внутри setjmp аналогично этому примеру для сохранения и восстановления контекста после того, как он был каким-то образом инициализирован.

Учитывая, что setjmp для дочерней функции обычно будет работать, если не саботируется, и setcontext, как часть POSIX, не требуется, чтобы его предоставляли реализации C, этот механизм может быть переносимым, если setcontext альтернатива не удалась.

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

#включают <setjmp.h>#включают <stdio.h>jmp_buf основная задача, childTask;пустота call_with_cushion();пустота ребенок();int главный() {    если (!setjmp(основная задача)) {        call_with_cushion(); // потомок никогда не возвращается, yield    } // выполнение возобновляется после этого "}" после первого раза, когда этот дочерний элемент дает    пока (1) {        printf("Родитель п");                если (!setjmp(основная задача))            longjmp(childTask, 1); // yield - обратите внимание, что это не определено в C99    }}пустота call_with_cushion() {    char Космос[1000]; // Зарезервировать достаточно места для запуска main    Космос[999] = 1; // Не убирать оптимизируемый массив    ребенок();}пустота ребенок() {    пока (1) {        printf("Начало дочернего цикла п");                если (!setjmp(childTask))            longjmp(основная задача, 1); // yield - аннулирует childTask в C99        printf("Дочерний цикл конец п");        если (!setjmp(childTask))            longjmp(основная задача, 1); // yield - аннулирует childTask в C99    }    / * Не возвращать. Вместо этого мы должны установить флаг, чтобы указать, что main ()       должен перестать уступать нам и тогда longjmp (mainTask, 1) * /}

Смотрите также

Рекомендации

  1. ^ а б ISO C утверждает, что setjmp должен быть реализован как макрос, но в POSIX явно указано, что не определено, будет ли setjmp это макрос или функция.
  2. ^ Это тип, используемый Библиотека GNU C, версия 2.7
  3. ^ а б Обоснование C99, версия 5.10, апрель 2003 г., раздел 7.13
  4. ^ Лекции CS360 - Setjmp и Longjmp
  5. ^ setjmp (3) В архиве 2009-07-26 на Wayback Machine
  6. ^ а б ISO / IEC 9899: 1999, 2005, 7.13.2.1:2 и сноска 211
  7. ^ setjmp: установить точку перехода для нелокального перехода - Справочник по системным интерфейсам, Единая спецификация UNIX, Выпуск 7 из Открытая группа

внешняя ссылка