О пользователе setjmp/longjmp

Я изучал setjmp/longjmp и обнаружил, что setjmp сохраняет регистры, такие как указатель инструкции, указатель стека и т.д.

Однако я не понимаю, что не могут быть изменены данные в стеке потока между вызовом setjmp и longjmp. В этом случае не будет longjmp работать не так, как ожидалось.

Чтобы было ясно, например, когда longjmp восстанавливает указатель стека, скажем, данные в памяти, которые указатель стека указывает сейчас, не совпадают с тем, когда setjmp. Может ли это случиться? И если это произойдет, разве мы не в беде?

Также под этим выражением подразумевается: "Процедуры longjmp() могут не вызываться после того, как процедура, вызванная подпрограммой setjmp(), возвращается."

Ответ 1

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

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

Ответ 2

setjmp()/longjmp() не предназначены для сохранения стека, для чего предназначены setcontext()/getcontext().

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

Ответ 3

Функция setjmp/longjmp (в дальнейшем slj) в C является уродливой, и ее поведение может различаться между реализациями. Тем не менее, учитывая отсутствие исключений, slj иногда требуется в C (обратите внимание, что С++ предоставляет исключения, которые почти всегда превосходят slj и что slj плохо взаимодействует со многими функциями С++).

При использовании slj следует иметь в виду следующее, предполагая, что обычная функция Setter() вызывает функцию Setter(), которая вызывает setjmp(), а затем вызывает перемычку, которая в свою очередь вызывает longjmp().

  • Код может законным образом выйти из области, в которой выполняется setjmp без выполнения longjmp; однако, как только выйдет область видимости, ранее созданный jmp_buf должен считаться недействительным. Компилятор, вероятно, ничего не сделает, чтобы пометить его как таковой, но любая попытка его использования может привести к непредсказуемому поведению, которое может включать в себя переход к произвольному адресу.
  • Любые локальные переменные в Jumper() будут испаряться при вызове longjmp(), делая их значения несущественными.
  • Всякий раз, когда элемент управления возвращается к родительскому, любыми способами, локальные переменные родителя будут такими, какими они были, когда они вызывали Setter, если только такие переменные не имели своих адресов и были изменены с использованием таких указателей; в любом случае setjmp/longjmp никак не повлияет на их значения. Если такие переменные не имеют принятых адресов, возможно, что setjmp() может кэшировать значения таких переменных, и longjmp() может их восстановить. Однако в этом сценарии переменные не будут переключаться между кешированием и восстановлением, поэтому кэш/восстановление не будет иметь видимого эффекта.
  • Переменные в Setter могут или не могут быть кэшированы вызовом setjmp(). После вызова longjmp() такие переменные могут иметь значение, которое они имели при выполнении setjmp(), или значения, которые они имели при вызове подпрограммы, которая в конечном счете называется longjmp(), или любая их комбинация. По крайней мере, на некоторых диалектах C такие переменные могут быть объявлены "изменчивыми", чтобы предотвратить их кэширование.

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

Ответ 4

Это объясняет вещи довольно хорошо.

Ответ 5

В приведенном ниже примере setjmp/longjump изменяет значение i, которое живет в основном, с помощью указателя. Я никогда не увеличивается в цикле for. Дополнительную забаву см. В записи albert.c, http://www.ioccc.org/years-spoiler.html победителя IOCCC 1992 года. (один из немногих раз, когда я ROTFLed читал источник C...)

#include <stdio.h>
#include <setjmp.h>

jmp_buf the_state;

void helper(int *p);
int main (void)
{
int i;

for (i =0; i < 10;    ) {
    switch (setjmp (the_state) ) {
    case 0:  helper (&i) ; break;
    case 1:    printf( "Even=\t"); break;
    case 2:    printf( "Odd=\t"); break;
    default: printf( "Oops=\t"); break;
        }
    printf( "I=%d\n", i);
    }

return 0;
}
void helper(int *p)
{
*p += 1;
longjmp(the_state, 1+ *p%2);
}