О setjmp / longjmp

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

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

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

Также то, что имеется в виду под заявлением: «Подпрограммы longjmp () не могут быть вызваны после возврата подпрограммы, которая вызвала подпрограммы setjmp ().»


person MetallicPriest    schedule 01.11.2011    source источник
comment
Это очень похоже на оператор. Локальная переменная не может использоваться после возврата процедуры, выделившей локальную переменную. Переменные стека, которые находятся в области видимости при вызове setjmp(), должны оставаться в области видимости при вызове longjmp().   -  person bk1e    schedule 01.11.2011


Ответы (4)


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

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

person R.. GitHub STOP HELPING ICE    schedule 01.11.2011
comment
Спасибо за ответ, но не могли бы вы объяснить, что подразумевается под вызовом longjmp после возврата функции, вызвавшей setjmp? И где в этом случае использовать volatile. Какие переменные я имею в виду. - person MetallicPriest; 01.11.2011
comment
Он имеет в виду вызов его из функции, внешней по отношению к вызову setjmp. - person ninjalj; 01.11.2011
comment
@MetallicPriest: вроде объясняется на странице Википедии о продолжениях: Более ограниченный вид - это escape-продолжение, которое может использоваться для выхода из текущего контекста в окружающий. Многие языки, которые явно не поддерживают продолжения, поддерживают обработку исключений, которая эквивалентна escape-продолжениям и может использоваться для тех же целей. Setjmp / longjmp в C также эквивалентны: их можно использовать только для раскрутки стека. Продолжение экранирования также можно использовать для реализации оптимизации хвостового вызова - person ninjalj; 01.11.2011
comment
@ninjali, вы имеете в виду, например, это ... void somefunction () {setjmp (); } int main () {некоторая функция (); longjmp (...)} неверно? - person MetallicPriest; 01.11.2011
comment
@MetallicPriest: да, стек для somefunction() уже уничтожен, и пути назад нет. Используйте для этого setcontext(). - person ninjalj; 01.11.2011
comment
_1 _ / _ 2_ для этого тоже не подойдет. Вам нужно будет создать полностью отдельный стек с _3 _... - person R.. GitHub STOP HELPING ICE; 01.11.2011
comment
@MetallicPriest: В самом деле, это неправильно и крайне опасно. Для того, чтобы что-то подобное работало, у C не могло быть линейного стека; он должен был бы иметь отдельные выделенные блоки в куче для каждого кадра вызова и своего рода сборку мусора, чтобы определить, какие из них все еще используются. Поскольку это невозможно на языке с представлением типов, в основном все программы просто увеличивают O (n) в памяти с количеством вызовов функций _1 _..., что было бы не очень весело. - person R.. GitHub STOP HELPING ICE; 01.11.2011

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

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

person ninjalj    schedule 01.11.2011
comment
Ответ на этот вопрос (stackoverflow.com/questions/15115480/) говорит setcontext()/getcontext() не сохранять стек. Что это такое и в чем разница между setcontext()/getcontext() и setjmp()/longjmp()? - person apple16; 02.12.2013
comment
@ apple16: setjmp()/longjmp() предназначены для раскрутки стека. getcontext()/setcontext()/makecontext()/swapcontext() предназначены для переключения между контекстами сопрограмм (каждый со своим собственным стеком), некоторые из которых созданы makecontext(). - person ninjalj; 02.12.2013

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

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

  1. Code may legally exit the scope in which a setjmp is performed without a longjmp having been executed; as soon as the scope exits, however, the previously-created jmp_buf must be regarded as invalid. The compiler probably won't do anything to mark it as such, but any attempt to use it may result in unpredictable behavior, likely including a jump to an arbitrary address.
  2. Any local variables in Jumper() will evaporate with the call to longjmp(), rendering their values irrelevant.
  3. Whenever control returns to Parent, via whatever means, Parent's local variables will be as they were when it called Setter, unless such variables had their addresses taken and were changed using such pointers; in any case, setjmp/longjmp will not affect their values in any way. If such variables do not have their addresses taken, it is possible that setjmp() may cache the values of such variables and longjmp() may restore them. In that scenario, however, there would be no way for the variables to change between when they are cached and when they are restored, so the cache/restore would have no visible effect.
  4. The variables in Setter may or may not be cached by setjmp() call. After a longjmp() call, such variables may have the value they had when setjmp() was performed, or the values they had when it called the routine which ultimately called longjmp(), or any combination thereof. In at least some C dialects, such variables may be declared "volatile" to prevent them from being cached.

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

person supercat    schedule 01.11.2011
comment
(4) неверно. Использование volatile требуется по стандарту, и на самом деле это единственный эффект, который ключевое слово volatile должно иметь по стандарту, который может быть протестирован строго соответствующими программами. Изменение локальных переменных и доступ к ним после longjmp, если volatile не использовался, приводит к UB. - person R.. GitHub STOP HELPING ICE; 01.11.2011
comment
Я также довольно смущен тем, что вы имеете в виду, говоря, что его поведение может варьироваться в зависимости от реализации. Определенное поведение совершенно не меняется. Просто нужно рассмотреть множество случаев UB, но все они совершенно очевидны, если вы понимаете, что делаете ... - person R.. GitHub STOP HELPING ICE; 01.11.2011
comment
@R: Я сказал хоть какие-то диалекты. Я не знаю, было ли такое поведение указано в исходном стандарте K&R C или в первом стандарте ANSI, или оно было впервые указано позднее, но это определенно верно по крайней мере для некоторых диалектов. Что касается UB, я не знал, делает ли изменение локальной переменной в области видимости, которая выполнила setjmp, недействительным созданный jmp_buf, или это просто приводит к тому, что значения измененных переменных становятся неопределенными (так что чтение перед записью будет Undefined Behavior) или что-то в этом роде. - person supercat; 01.11.2011
comment
@R: Возможно, мне следовало бы яснее сказать, что помимо возможностей, перечисленных для возможного содержимого переменных после вызова longjmp (), переменные могут также содержать назальных демонов. На практике я думаю, что тот факт, что они могут содержать любую комбинацию старых и новых данных, будет достаточным, чтобы воспрепятствовать их использованию, даже если они не могут содержать носовых демонов. Что касается различий между реализациями, я думал о таких вещах, как переменные в области видимости, вызывающей setjmp (). Некоторые реализации позволяют обойтись без изменчивых квалификаторов, а пользователи таких реализаций ... - person supercat; 01.11.2011
comment
@R: ... может сойти с рук, игнорируя их годами, никогда не осознавая, что они полагаются на Undefined Behavior. - person supercat; 01.11.2011

В приведенном ниже примере setjmp / longjump изменяет значение i, которое находится в main, с помощью указателя. I никогда не увеличивается в цикле for. Для дополнительного развлечения см. Запись albert.c, http://www.ioccc.org/years-spoiler.html победитель конкурса IOCCC 1992 года. (один из немногих случаев, когда я когда-либо читал исходники на 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);
}
person wildplasser    schedule 01.11.2011
comment
i должен быть volatile, чтобы эта программа была действительной. - person R.. GitHub STOP HELPING ICE; 01.11.2011
comment
Может быть. Но есть указатель un noalias-sed на него, плавающий вокруг. Жалко, что DMR умер ... - person wildplasser; 01.11.2011
comment
Компилятор мог бы, видя, что i не изменяется в случаях 1 и 2, переместить printf внутрь тела в этих случаях и повторно использовать кэшированное в регистре значение i из теста цикла ... Было бы хорошо, если бы компилятор обработал setjmp особенно здесь, но мое понимание требования использования volatile состоит в том, что он существует, поэтому компиляторам не нужно быть умными с setjmp (и поскольку некоторые из угловых случаев достаточно сложны для решения, компилятор не может этого сделать; они, вероятно, требуют решения проблемы остановки). - person R.. GitHub STOP HELPING ICE; 01.11.2011
comment
Кстати: программа была предназначена не для того, чтобы дать ответ, а скорее для того, чтобы вызвать обсуждение. В любом случае он демонстрирует, что автоматическая переменная main () (я избегаю фрейма стека слов, чтобы угодить языковым юристам) может быть изменена между setjmp и longjmp. Семантика Volatile у IIRC довольно неоднозначна. - person wildplasser; 01.11.2011