Как работают нелокальные переходы в C, определенные в setjmp.h?

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

Итак, что именно делает эта функция и где она полезна?


person Tapan Chandra    schedule 19.05.2013    source источник
comment
Я реализовал библиотеку потоков пользовательского режима, используя setjmp и длинный прыжок. Никакой сборки, переключение контекста достигается с помощью lngjmp   -  person stdcall    schedule 19.05.2013


Ответы (1)


Что касается потока управления: setjmp возвращается дважды, а longjmp никогда не возвращается. Когда вы вызываете setjmp в первый раз, чтобы сохранить среду, он возвращает ноль, а когда вы вызываете longjmp, поток управления переходит к возврату из setjmp со значением, указанным в аргументе.

(Обратите внимание, что setjmp на самом деле не обязательно должны быть функциями; это вполне может быть макрос. Однако longjmp — это функция.)

Варианты использования обычно упоминаются как «обработка ошибок» и «не использовать эти функции».

Вот небольшой пример потока управления:

jmp_buf env;

void foo()
{
    longjmp(&env, 10);                      +---->----+
}                                           |         |
                                            |         |
int main()              (entry)---+         ^         V
{                                 |         |         |
    if(setjmp(&env) == 0)         | (= 0)   |         | (= 10)
    {                             |         ^         |
        foo();                    +---->----+         |
    }                                                 +---->----+
    else                                                        |
    {                                                           |
        return 0;                                               +--- (end)
    }
}

Заметки:

  • Вы не можете передать 0 в longjmp. Если вы это сделаете, 1 будет возвращено setjmp.

  • Вы не должны возвращаться из функции, вызвавшей setjmp, до соответствующего longjmp. Другими словами, longjmp должен вызываться только выше setjmp в стеке вызовов.

  • (Спасибо @wildplasser :) На самом деле вы не можете сохранить результат setjmp. Если вы хотите вернуться несколькими разными способами, вы можете использовать switch:

    switch (setjmp(&env))
    {
    case 0:   // first call
    case 2:   // returned from longjmp(&env, 2)
    case 5:   // returned from longjmp(&env, 5)
    // etc.
    }
    
person Kerrek SB    schedule 19.05.2013
comment
Неправильный. Вы не должны назначать возвращаемое значение из setjmp(). Вы можете только оценить его. - person wildplasser; 19.05.2013
comment
@wildplasser: Интересно, у вас есть ссылка на это? - person Kerrek SB; 19.05.2013
comment
Погуглил... Я попал в c.l.c несколько лет назад. БРБ - person wildplasser; 19.05.2013
comment
@wildplasser: Вы правы, это может быть запрещено 7.13.1.1/4 в C11. - person Kerrek SB; 19.05.2013
comment
Вот один из них: securecoding.cert.org/confluence/display/seccode/ . Вот зеркало Usenet: coding.derkeiler. com/Архив/C_CPP/comp.lang.c/2009-07/ - person wildplasser; 19.05.2013
comment
Спасибо за подробный ответ. Я действительно ценю усилия. Но чем это лучше, чем простой вызов функции + возврат, учитывая, что у setjmp есть ограничение, заключающееся в невозможности присвоить возвращаемое значение? - person Tapan Chandra; 19.05.2013
comment
@rusticbit: я не думаю, что это лучше ... это просто еще один инструмент, и вы можете использовать его не локально, то есть вы можете вернуться издалека. Это немного похоже на исключение С++ для бедных. (И, как сказано в документации, вам рекомендуется не его использовать.) - person Kerrek SB; 19.05.2013
comment
Я полагаю, вы имели в виду if( !setjmp(&env)) ... ? - person wildplasser; 19.05.2013