setjmp.h - это заголовок, определенный в стандартной библиотеке C для обеспечения «нелокальных переходов»: поток управления, который отличается от обычной последовательности вызова и возврата подпрограммы . Дополнительные функции setjmp
и longjmp
обеспечивают эту функциональность.
Типичное использование setjmp
/ longjmp
- это реализация механизма исключений, который использует возможность longjmp
восстановления состояния программы или потока даже на нескольких уровнях вызовов функций. Менее распространенное использование setjmp
- создание синтаксиса, аналогичного сопрограммам .
Функции-члены
int setjmp(jmp_buf env) | Устанавливает локальный jmp_buf буфер и инициализирует его для перехода. Эта процедура [1] сохраняет среду вызова программы в буфере среды, заданном env аргументом, для последующего использования longjmp . Если возврат от прямого вызова, setjmp возвращает 0. Если возврат от вызова longjmp , setjmp возвращает ненулевое значение. |
void longjmp(jmp_buf env, int value) | Восстанавливает контекст буфера среды, env который был сохранен при вызове setjmp подпрограммы [1] в том же вызове программы. Вызов longjmp из вложенного обработчика сигналов не определен . Значение, указанное в, value передается из longjmp в setjmp . После longjmp завершения выполнение программы продолжается, как если бы соответствующий вызов setjmp только что вернулся. Если value передано longjmp 0, setjmp будет вести себя так, как если бы он вернул 1; в противном случае он будет вести себя так, как если бы он вернулся value . |
setjmp
сохраняет текущую среду (состояние программы) в какой-то момент выполнения программы в структуру данных, зависящую от платформы ( jmp_buf
), которую можно использовать на более позднем этапе выполнения программы, longjmp
чтобы восстановить состояние программы до состояния, сохраненного setjmp
в jmp_buf
. Этот процесс можно представить как «прыжок» назад к точке выполнения программы, где setjmp
сохранена среда. (Кажущееся) возвращаемое значение из setjmp
указывает, достигло ли управление этой точки нормально (ноль) или от вызова longjmp
(ненулевое). Это приводит к общему идиомы : .if( setjmp(x) ){/* handle longjmp(x) */}
POSIX +0,1 не определяет , является ли setjmp
и longjmp
сохранить и восстановить текущий набор блокированных сигналов ; если программа использует обработку сигналов, она должна использовать 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 , Java , C ++ , C # , и даже в языках программирования до C, таких как Algol 60 , метод использования setjmp
/ longjmp
для реализации механизма исключения является громоздким. Эти языки предоставляют более мощные методы обработки исключений , в то время как такие языки, как Scheme , Smalltalk и Haskell, предоставляют даже более общие конструкции обработки продолжения .
Пример использования
Простой пример
В приведенном ниже примере показана основная идея setjmp. Там main()
звонки first()
, которые в свою очередь звонки second()
. Затем second()
прыгает обратно main()
, пропуская first()
зов printf()
.
#include #include статический jmp_buf buf ;void second () { printf ( "второй \ п " ); // выводит longjmp ( buf , 1 ); // возвращается туда, где был вызван setjmp - теперь setjmp возвращает 1 }недействительный первый () { второй (); printf ( "первый \ п " ); // не печатает }int main () { если ( ! setjmp ( buf )) первый (); // при выполнении setjmp вернул 0 else // когда longjmp перескочил назад, setjmp вернет 1 printf ( "main \ n " ); // печатает возврат 0 ; }
При выполнении вышеуказанная программа выведет:
второйосновной
Обратите внимание, что хотя first()
подпрограмма first
вызывается , " " никогда не печатается. " main
" печатается при if (!setjmp(buf))
повторном выполнении условного оператора .
Обработка исключений
В этом примере setjmp
используется для скобок обработки исключений, как и try
в некоторых других языках. Вызов longjmp
аналогичен throw
оператору, позволяя исключению возвращать статус ошибки непосредственно в setjmp
. Следующий код соответствует стандарту ISO C 1999 г. и единой спецификации UNIX , вызывая его setjmp
в ограниченном диапазоне контекстов: [7]
- В качестве условия к
if
,switch
или цикла - Как указано выше, в сочетании с одиночным значением
!
или сравнением с целочисленной константой - Как утверждение (с неиспользованным возвращаемым значением)
Следование этим правилам может упростить реализацию для создания буфера среды, что может быть деликатной операцией. [3] Более общее использование 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, никаких операций не выполняется возврат 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 ( "ввод секунды" ); // достиг исключение_типа = 3 ; longjmp ( исключение_env , тип_исключения ); // объявляем, что программа потерпела неудачу put ( "уходящий второй" ); // не достигнуто }
Результат этой программы:
звоню первымвходящий первымвызывающий второйвходящий второйвторой сбой, тип исключения: 3; переназначение на тип 1первая ошибка, тип исключения: 1
Хотя эта exception_type
переменная здесь технически не нужна, поскольку setjmp возвращает ненулевое значение, с которым был вызван longjmp (как во втором и первом), на практике будет использоваться более сложный глобальный объект для размещения более богатых исключений.
В реальном мире setjmp-longjmp (sjlj) использовался как стандартный способ обработки исключений в сторонних компиляторах Windows C ++ (а именно, MinGW ), поскольку встроенная обработка структурированных исключений в целом плохо документирована и также была запатентована на 32-й версии. бит Windows до 2014 года.
Совместная многозадачность
C99 гарантирует, что работа longjmp
будет гарантирована только тогда, когда адресатом является вызывающая функция, т. Е. Что область назначения гарантированно останется неповрежденной. Переход к функции, которая уже завершена return
или longjmp
не определена. [6] Однако большинство реализаций 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 (); недействительный ребенок ();int main () { если ( ! setjmp ( mainTask )) { call_with_cushion (); // потомок никогда не возвращается, yield } // выполнение возобновляется после этого "}" после первого раза, когда этот дочерний элемент дает в то время как ( 1 ) { printf ( "Родитель \ п " ); если ( ! 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) * / }
Смотрите также
- Продолжение
- setcontext
Рекомендации
- ^ a b В ISO C указано, что это
setjmp
должно быть реализовано как макрос, но в POSIX явно указано, что не определено,setjmp
является ли это макросом или функцией. - ^ Это тип, используемый библиотекой GNU C версии 2.7.
- ^ a b C99 Обоснование, версия 5.10, апрель 2003 г. , раздел 7.13
- ^ CS360 Лекции - Setjmp и Longjmp
- ^ setjmp (3) Архивировано 26 июля 2009 г. на Wayback Machine
- ^ a b ISO / IEC 9899: 1999 , 2005, 7.13.2.1:2 и сноска 211
- ^ : установить точку перехода для нелокального перехода - Справочник по системным интерфейсам, Единая спецификация UNIX , Выпуск 7 из Open Group
Внешние ссылки
- : установить точку перехода для нелокального перехода - Справочник по системным интерфейсам, Единая спецификация UNIX , Выпуск 7 из The Open Group
- Исключения в C с Longjmp и Setjmp
- есть ли sigsetjmp / siglongjmp (снова) (об этих функциях в mingw / MSYS )