setjmp.h - это заголовок , определенный в стандартной библиотеке C для обеспечения «нелокальных переходов»: поток управления, который отличается от обычной подпрограммы, вызывающей и возвращающей последовательность. Дополнительные функции setjmp
и longjmp
обеспечивают эту функциональность.
Типичное использование setjmp
/ longjmp
- это реализация механизма исключения, который использует способность longjmp
к восстановить состояние программы или потока даже на нескольких уровнях вызовов функций. Менее распространенное использование setjmp
- создание синтаксиса, аналогичного сопрограмм.
int setjmp (jmp_buf env) | Устанавливает локальный буфер jmp_buf и инициализирует его для перехода. Эта подпрограмма сохраняет среду вызова программы в буфере среды, заданном аргументом env , для дальнейшего использования longjmp . Если возврат от прямого вызова, setjmp возвращает 0. Если возврат от вызова к longjmp , setjmp возвращает ненулевое значение. |
void longjmp (jmp_buf env, int value) | Восстанавливает контекст буфера среды env , который был сохранен при вызове процедуры setjmp в том же вызове программа. Вызов longjmp из вложенного обработчика сигналов имеет значение undefined. Значение, указанное в value , передается из longjmp в setjmp . После завершения longjmp выполнение программы продолжается, как если бы соответствующий вызов setjmp только что вернулся. Если значение , переданное в longjmp , равно 0, setjmp будет вести себя так, как если бы он вернул 1; в противном случае он будет вести себя так, как если бы он возвратил value . |
setjmp
сохраняет текущую среду (состояние программы) в какой-то момент выполнения программы в структуре данных, зависящей от платформы (jmp_buf
), который может быть использован на более позднем этапе выполнения программы с помощью longjmp
для восстановления состояния программы до состояния, сохраненного setjmp
в jmp_buf
. Этот процесс можно представить как «прыжок» назад к точке выполнения программы, где setjmp
сохранил среду. (Кажущееся) , возвращаемое значение из setjmp
указывает, достигло ли управление этой точки нормально (ноль) или от вызова longjmp
(ненулевое). Это приводит к общей идиоме : if (setjmp (x)) {/ * handle longjmp (x) * /}
.
POSIX.1 не указывает, должен ли setjmp
и longjmp
сохраняют и восстанавливают текущий набор заблокированных сигналов ; если программа использует обработку сигналов, она должна использовать POSIX sigsetjmp
/ siglongjmp
.
jmp_buf | Тип массива, например struct __jmp_buf_tag [1] , подходит для хранения информации, необходимой для восстановления среды вызова. |
Обоснование C99 описывает jmp_buf
как тип массива для обратной совместимости ; существующий код ссылается на места хранения jmp_buf
по имени (без оператора адреса ), что возможно только для типов массивов.
Когда "нелокальный переход" выполняется через setjmp
/ longjmp
в C ++, нормальный "раскрутка стека " не происходит. Следовательно, никаких необходимых действий по очистке также не произойдет. Это может включать закрытие файловых дескрипторов, очистку буферов или освобождение памяти, выделенной кучей.
. Если функция, в которой был вызван setjmp
, возвращается, больше невозможно безопасно использовать longjmp
с соответствующим объектом jmp_buf
. Это связано с тем, что кадр стека становится недействительным при возврате функции. Вызов longjmp
восстанавливает указатель стека , который - поскольку возвращенная функция - указывает на несуществующий и потенциально перезаписанный или поврежденный кадр стека.
Аналогично, C99 не требует, чтобы longjmp
сохранял текущий кадр стека. Это означает, что переход в функцию, которая была завершена вызовом longjmp
, не определена. Однако в большинстве реализаций longjmp
кадр стека остается нетронутым, что позволяет использовать setjmp
и longjmp
для перехода между двумя или более функциями - функция, используемая для многозадачности.
По сравнению с механизмами в языках программирования более высокого уровня, таких как Python, Java, C ++, C# и даже до В языках C, таких как Algol 60, метод использования setjmp
/ longjmp
для реализации механизма исключения является громоздким. Эти языки предоставляют более мощные методы обработки исключений, в то время как такие языки, как Scheme, Smalltalk и Haskell, предоставляют еще более общие Continuation - обрабатывающие конструкции.
В приведенном ниже примере показана основная идея setjmp. Там main ()
вызывает first ()
, который, в свою очередь, вызывает second ()
. Затем second ()
переходит обратно в main ()
, пропуская вызов first ()
функции printf ()
.
#include#include static jmp_buf buf; void second () {printf ("второй \ п"); // выводит longjmp (buf, 1); // переходит обратно к тому месту, где был вызван setjmp - теперь setjmp возвращает 1} void first () {second (); printf ("первый \ п"); // не печатает} int main () {if (! setjmp (buf)) first (); // при выполнении setjmp вернул 0 else // когда longjmp перескочил назад, setjmp вернет 1 printf ("main \ n"); // выводит return 0; }
При выполнении вышеуказанная программа выведет:
second main
Обратите внимание, что, хотя вызывается подпрограмма first ()
, «first
» никогда не печатается.. «main
» печатается как условный оператор , если (! Setjmp (buf))
выполняется во второй раз.
В этом примере setjmp
используется для обработки исключений в скобках, например попробуйте
на некоторых других языках. Вызов longjmp
аналогичен оператору throw
, позволяя исключению возвращать статус ошибки непосредственно в setjmp
. Следующий код соответствует стандарту 1999 ISO C и Single UNIX Specification, вызывая setjmp
в ограниченном диапазоне контекстов:
if
, переключатель
или оператор итерации!
или сравнением с целочисленной константойСледование этим правилам может упростить реализацию для создания буфера среды, что может быть деликатной операцией. Более общее использование setjmp
может вызвать неопределенное поведение, такое как повреждение локальных переменных; соответствующие компиляторы и среды не обязаны защищать или даже предупреждать о таком использовании. Однако несколько более сложные идиомы, такие как switch ((exception_type = setjmp (env))) {}
, распространены в литературе и на практике и остаются относительно переносимыми. Ниже представлена простая соответствующая методология, в которой вместе с буфером состояний поддерживается дополнительная переменная. Эта переменная может быть преобразована в структуру, включающую сам буфер.
В более современном примере обычный блок «try» будет реализован как setjmp (с некоторым кодом подготовки для многоуровневых прыжков, как показано в first
), «throw "as longjmp с необязательным параметром в качестве исключения и блок" catch "в качестве блока" else "в разделе" try ".
#include#include #include #include static void first (); static void second (); / * Используем статическую переменную с файловой областью для стека исключений, чтобы мы могли * получить к нему доступ в любом месте этой единицы трансляции. * / static jmp_buf exception_env; статический int exception_type; int main (void) {char * volatile mem_buffer = NULL; if (setjmp (exception_env)) {// если мы попали сюда, то произошло исключение printf ("первая ошибка, тип исключения:% d \ n", exception_type); } else {// Запускаем код, который может сигнализировать об ошибке через longjmp. put ("сначала звонит"); первый(); mem_buffer = malloc (300); // выделяем ресурс printf ("% s \ n", strcpy (mem_buffer, "первый успешно")); // не достигнут} бесплатно (mem_buffer); // NULL можно передать в free, никаких операций не выполняется return 0; } static void first () {jmp_buf my_env; put ("входящий первым"); // достигли memcpy (my_env, exception_env, sizeof my_env); switch (setjmp (exception_env)) {case 3: // если мы попали сюда, возникло исключение. put ("второй сбой, тип исключения: 3; переназначение на тип 1"); исключение_типа = 1; default: // пропадать через memcpy (exception_env, my_env, sizeof exception_env); // восстанавливаем стек исключений longjmp (exception_env, exception_type); // продолжить обработку исключения case 0: // нормальная, желаемая операция put ("вызывающая вторая"); // достигли second (); put ("вторая удалась"); // не достигнуто} memcpy (exception_env, my_env, sizeof exception_env); // восстановление стека исключений put ("уход первым"); // никогда не достигал} static void second () {put ("ввод второй"); // достигнуто exception_type = 3; longjmp (исключение_env, тип_исключения); // объявляем, что программа не удалась put ("уходит второй"); // не достигнуто}
Эта программа выводит:
вызов первого ввода первого вызова второго ввода второго второго не удалось, тип исключения: 3; сначала не удалось переназначить тип 1, тип исключения: 1
Хотя переменная exception_type
здесь технически не нужна, поскольку setjmp возвращает ненулевое значение, с которым был вызван longjmp (как во втором и первом), на практике для обработки более обширных исключений будет использоваться более сложный глобальный объект.
В реальном мире setjmp-longjmp (sjlj) использовался как стандартный способ обработки исключений в сторонних компиляторах Windows C ++, поскольку нативная Обработка структурированных исключений не только плохо документирована. в общем, но также запатентовано на 32-битной Windows.
C99 предусматривает, что longjmp
гарантированно будет работать только тогда, когда адресатом является вызывающая функция, т.е. что область назначения гарантированно останется неизменной. Переход к функции, которая уже завершилась с помощью return
или longjmp
, не определена. Однако большинство реализаций longjmp
специально не уничтожают локальные переменные при выполнении перехода. Поскольку контекст сохраняется до тех пор, пока его локальные переменные не будут удалены, его можно восстановить с помощью setjmp
. Во многих средах (например, Really Simple Threads и TinyTimbers ) такие идиомы, как if (! Setjmp (child_env)) longjmp (caller_env);
могут разрешать вызываемая функция для эффективной приостановки и возобновления на setjmp
.
Это используется библиотеками потоков для обеспечения возможностей совместной многозадачности без использования setcontext
или другие объекты волокна. В то время как setcontext
является библиотечной службой, которая может создавать контекст выполнения в памяти, выделенной кучей, и может поддерживать другие службы, такие как защита от переполнения буфера, злоупотребление setjmp
является допустимым. реализуется программистом, который может зарезервировать память в стеке и не уведомить библиотеку или операционную систему о новом рабочем контексте. С другой стороны, реализация библиотеки setcontext
может внутренне использовать setjmp
аналогично этому примеру для сохранения и восстановления контекста после того, как он был каким-то образом инициализирован.
Учитывая, что setjmp
для дочерней функции обычно будет работать, если не саботируется, и setcontext
, как часть POSIX, не требуется предоставляемый реализациями C, этот механизм может быть переносимым, если альтернатива setcontext
не работает.
Поскольку при переполнении одного из нескольких стеков в таком механизме исключение не генерируется, важно переоценить пространство, необходимое для каждого контекста, включая тот, который содержит main ()
и включая место для любых обработчиков сигналов, которые могут прервать обычное выполнение. Превышение выделенного пространства повредит другие контексты, обычно в первую очередь внешние функции. К сожалению, системы, требующие такой стратегии программирования, также часто бывают небольшими с ограниченными ресурсами.
#include#include jmp_buf mainTask, childTask; void call_with_cushion (); void child (); int main () {если (! setjmp (mainTask)) {call_with_cushion (); // дочерний процесс никогда не возвращается, yield} // выполнение возобновляется после этого "}" после первого раза, когда этот дочерний элемент дает результат while (1) {printf ("Parent \ n"); если (! setjmp (mainTask)) longjmp (childTask, 1); // yield - обратите внимание, что это не определено в C99}} void call_with_cushion () {char space [1000]; // Зарезервируйте достаточно места для main для запуска space [999] = 1; // Не допускать отсутствия оптимизации массива child (); } void child () {while (1) {printf ("Начало дочернего цикла \ n"); если (! setjmp (childTask)) longjmp (mainTask, 1); // yield - аннулирует childTask в C99 printf ("Конец дочернего цикла \ n"); если (! setjmp (childTask)) longjmp (mainTask, 1); // yield - делает childTask недействительным в C99} / * Не возвращать. Вместо этого мы должны установить флаг, указывающий, что main () должен перестать уступать нам, а затем longjmp (mainTask, 1) * /}
setjmp
: установка точки перехода для нелокального перехода - Справочник по системным интерфейсам, Единая спецификация UNIX, выпуск 7 из Открытая группа