setjmp.h
Эта статья нуждается в дополнительных цитатах для проверки . ( декабрь 2016 г. ) |
Стандартная библиотека C (libc) |
---|
Общие темы |
Разные заголовки |
|
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 .1 не определяет, setjmp
и longjmp
сохранить и восстановить текущий набор заблокированных сигналов ; если программа использует обработку сигналов, она должна использовать POSIX sigsetjmp
/ siglongjmp
.
Типы участников
[ редактировать ]jmp_buf |
Тип массива, подходящий для хранения информации, необходимой для восстановления вызывающей среды. |
Обоснование C99 описывает jmp_buf
как тип массива для обратной совместимости ; существующий код относится к jmp_buf
места хранения по названию (без &
оператор адреса), что возможно только для типов массивов. [2] Он отмечает, что это может быть просто массив длиной в один элемент, единственный элемент которого является фактическими данными; действительно, это подход, используемый библиотекой GNU C , которая определяет тип как struct __jmp_bug_tag[1]
.
Предостережения и ограничения
[ редактировать ]Когда «нелокальный переход» выполняется через setjmp
/ longjmp
в C++ нормального « раскручивания стека » не происходит. Таким образом, никаких необходимых действий по очистке также не произойдет. Это может включать в себя закрытие файловых дескрипторов , очистку буферов или освобождение памяти, выделенной в куче .
Если функция, в которой setjmp
назывался возвратом, безопасно использовать уже невозможно longjmp
с соответствующим jmp_buf
объект. Это связано с тем, что кадр стека становится недействительным при возврате функции. Вызов longjmp
восстанавливает указатель стека , который — поскольку функция возвращает — будет указывать на несуществующий и потенциально перезаписанный или поврежденный кадр стека. [3] [4]
Аналогично, C99 не требует, чтобы longjmp
сохранить текущий кадр стека. Это означает, что переход в функцию, выход из которой был выполнен посредством вызова longjmp
является неопределенным. [5]
Пример использования
[ редактировать ]Простой пример
[ редактировать ]В примере ниже показана основная идея setjmp. Там, main()
звонки first()
, который, в свою очередь, вызывает second()
. Затем, second()
прыгает обратно в main()
, пропуская first()
это вызов printf()
.
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
void second() {
printf("second\n"); // prints
longjmp(buf, 1); // jumps back to where setjmp was called - making setjmp now return 1
}
void first() {
second();
printf("first\n"); // does not print
}
int main() {
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints
return 0;
}
При выполнении вышеуказанная программа выведет:
second main
Обратите внимание, что, хотя first()
вызывается подпрограмма, " first
"никогда не печатается." main
" печатается как условный оператор if (!setjmp(buf))
выполняется второй раз.
Обработка исключений
[ редактировать ]В этом примере setjmp
используется для заключения в скобки обработки исключений, например try
на некоторых других языках. Звонок в longjmp
аналогичен throw
оператор, позволяющий исключению возвращать статус ошибки непосредственно в setjmp
. Следующий код соответствует стандарту ISO C 1999 года и единой спецификации UNIX, вызывая setjmp
в ограниченном диапазоне контекстов: [6]
- В качестве условия
if
,switch
или оператор итерации - Как указано выше, в сочетании с одним
!
или сравнение с целочисленной константой - Как оператор (с неиспользованным возвращаемым значением)
Следование этим правилам может облегчить реализацию создания буфера среды, что может быть конфиденциальной операцией. [2] Более общее использование setjmp
может вызвать неопределенное поведение, например повреждение локальных переменных; соответствующие компиляторы и среды не обязаны защищать или даже предупреждать о таком использовании. Однако более сложные идиомы, такие как switch ((exception_type = setjmp(env))) { }
распространены в литературе и практике и остаются относительно портативными. Ниже представлена простая соответствующая методология, в которой наряду с буфером состояния поддерживается дополнительная переменная. Эта переменная может быть преобразована в структуру, включающую сам буфер.
В более современном примере обычный блок «try» будет реализован как setjmp (с некоторым подготовительным кодом для многоуровневых переходов, как показано на рис. first
), «throw» как longjmp с необязательным параметром в качестве исключения и «catch» в качестве блока «else» в разделе «try».
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void first();
static void second();
/* Use a file scoped static variable for the exception stack so we can access
* it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;
int main(void) {
char* volatile mem_buffer = NULL;
if (setjmp(exception_env)) {
// if we get here there was an exception
printf("first failed, exception type: %d\n", exception_type);
} else {
// Run code that may signal failure via longjmp.
puts("calling first");
first();
mem_buffer = malloc(300); // allocate a resource
printf("%s\n", strcpy(mem_buffer, "first succeeded")); // not reached
}
free(mem_buffer); // NULL can be passed to free, no operation is performed
return 0;
}
static void first() {
jmp_buf my_env;
puts("entering first"); // reached
memcpy(my_env, exception_env, sizeof my_env);
switch (setjmp(exception_env)) {
case 3: // if we get here there was an exception.
puts("second failed, exception type: 3; remapping to type 1");
exception_type = 1;
default: // fall through
memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
longjmp(exception_env, exception_type); // continue handling the exception
case 0: // normal, desired operation
puts("calling second"); // reached
second();
puts("second succeeded"); // not reached
}
memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
puts("leaving first"); // never reached
}
static void second() {
puts("entering second" ); // reached
exception_type = 3;
longjmp(exception_env, exception_type); // declare that the program has failed
puts("leaving second"); // not reached
}
Вывод этой программы:
calling first entering first calling second entering second second failed, exception type: 3; remapping to type 1 first failed, exception type: 1
Кооперативная многозадачность
[ редактировать ]C99 предусматривает, что longjmp
гарантированно будет работать только тогда, когда пункт назначения является вызывающей функцией, т. е. область назначения гарантированно не повреждена. Переход к функции, которая уже завершилась return
или longjmp
является неопределенным. [5] Однако большинство реализаций longjmp
не уничтожайте локальные переменные при выполнении перехода. Поскольку контекст сохраняется до тех пор, пока его локальные переменные не будут удалены, его фактически можно восстановить с помощью setjmp
. Во многих средах (таких как Really Simple Threads и TinyTimbers ) такие идиомы, как if(!setjmp(child_env)) longjmp(caller_env);
может позволить вызываемой функции эффективно приостанавливаться и возобновляться в setjmp
.
Это используется библиотеками потоков для обеспечения совместной многозадачности без использования setcontext
или другие оптоволоконные средства.
Учитывая, что setjmp
дочерней функции, как правило, будет работать, если ее не саботировать, и setcontext
, как часть POSIX, не требуется предоставлять реализациями C, этот механизм может быть переносимым, если setcontext
альтернатива не удалась.
Поскольку в таком механизме при переполнении одного из нескольких стеков не будет сгенерировано никаких исключений, важно переоценить пространство, необходимое для каждого контекста, включая тот, который содержит main()
и включая место для любых обработчиков сигналов, которые могут прервать обычное выполнение. Превышение выделенного пространства приведет к повреждению других контекстов, обычно сначала самых внешних функций. К сожалению, системы, требующие такой стратегии программирования, зачастую также являются небольшими и имеют ограниченные ресурсы.
#include <setjmp.h>
#include <stdio.h>
jmp_buf mainTask, childTask;
void call_with_cushion();
void child();
int main() {
if (!setjmp(mainTask)) {
call_with_cushion(); // child never returns, yield
} // execution resumes after this "}" after first time that child yields
while (1) {
printf("Parent\n");
if (!setjmp(mainTask))
longjmp(childTask, 1); // yield - note that this is undefined under C99
}
}
void call_with_cushion() {
char space[1000]; // Reserve enough space for main to run
space[999] = 1; // Do not optimize array out of existence
child();
}
void child() {
while (1) {
printf("Child loop begin\n");
if (!setjmp(childTask))
longjmp(mainTask, 1); // yield - invalidates childTask in C99
printf("Child loop end\n");
if (!setjmp(childTask))
longjmp(mainTask, 1); // yield - invalidates childTask in C99
}
/* Don't return. Instead we should set a flag to indicate that main()
should stop yielding to us and then longjmp(mainTask, 1) */
}
Ссылки
[ редактировать ]- ^ Перейти обратно: а б ISO C утверждает, что
setjmp
должен быть реализован как макрос, но POSIX явно заявляет, что не определено, будет лиsetjmp
это макрос или функция. - ^ Перейти обратно: а б C99 Обоснование, версия 5.10, апрель 2003 г. , раздел 7.13
- ^ Конспекты лекций CS360 — Setjmp и Longjmp
- ^ setjmp(3). Архивировано 26 июля 2009 г. на Wayback Machine.
- ^ Перейти обратно: а б ISO/IEC 9899:1999 , 2005, 7.13.2.1:2 и сноска 211.
- ^ Единая спецификация UNIX , версия 4 от The Open Group : установить точку перехода для нелокального перехода — Справочник по системным интерфейсам,
Дальнейшее чтение
[ редактировать ]- Нидито, Франческо (2 июля 2016 г.). «Исключения в C с Longjmp и Setjmp» . Группы.Di.Unipi.it . Проверено 02 января 2024 г.
Внешние ссылки
[ редактировать ]- Единая спецификация UNIX , версия 4 от The Open Group : установить точку перехода для нелокального перехода — Справочник по системным интерфейсам,
- есть ли sigsetjmp/siglongjmp (опять) (об этих функциях в mingw / MSYS )