Рассмотрим следующий довольно распространенный сценарий, относящийся к обработке ошибки. В ходе глубоко вложенного вызова функции произошла ошибка, которая должна быть обработана за счет отказа от выполнения текущей задачи, возвращения сквозь несколько вызовов функций и последующего выполнения обработки в какой-нибудь функции более высокого уровня (возможно, даже main()). Добиться этого можно, если каждая функция станет возвращать код завершения, который будет проверяться и соответствующим образом обрабатываться вызывавшим ее кодом. Это вполне допустимый и во многих случаях желательный метод обработки такого рода развития событий. Но программирование давалось бы проще, если бы можно было перейти из середины вложенного вызова функции назад к одной из вызвавших ее функций (к непосредственно вызвавшей ее функции или к той функции, что вызвала эту функцию, и т. д.). Именно такая возможность и предоставляется функциями setjmp() и longjmp().
Ограничение, согласно которому goto не может использоваться для перехода между функциями, накладывается в языке C из-за того, что все функции в C находятся на одном и том же уровне области видимости (то есть стандарт языка C не предусматривает вложенных объявлений функций, хотя gcc допускает такую возможность в качестве расширения). Следовательно, если взять две функции, X и Y, то у компилятора не будет возможности узнать, может ли стековый фрейм для функции X быть в стеке ко времени вызова функции Y, а стало быть, возможен ли переход из функции Y в функцию X.
В таких языках, как Pascal, где объявления функций могут быть вложенными и переход из вложенной функции на уровень выше разрешен, статическая область видимости функции позволяет компилятору определить информацию о динамической области видимости этой функции. Таким образом, если функция Y лексически вложена в функцию X, то компилятор знает, что стековый фрейм для X должен уже быть в стеке ко времени вызова функции Y, и может сгенерировать код для перехода из функции Y в какое-либо место внутри функции X.
#include
int setjmp(jmp_buf
Возвращает 0 при первом вызове, ненулевое значение при возвращении через longjmp()
void longjmp(jmp_buf
Вызов setjmp() устанавливает цель для последующего перехода, выполняемого функцией longjmp(). Этой целью является та самая точка в программе, откуда вызывается функция setjmp(). С точки зрения программирования после longjmp() это выглядит абсолютно так же, как будто мы только что вернулись из вызова setjmp() во второй раз. Способ, позволяющий отличить второе «возвращение» от первого, основан на целочисленном значении, возвращаемом функцией setjmp(). При первом вызове setjmp() возвращает 0, а при последующем, фиктивном возвращении предоставляется то значение, которое указано в аргументе val при вызове функции longjmp(). Путем использования для аргумента val различных значений можно отличать друг от друга переходы к одной и той же цели из различных мест программы.
Если бесконтрольно указать для функции longjmp() аргумент val, равный нулю, то это вызовет фиктивное возвращение из setjmp(), которое будет выглядеть, как будто это первое возвращение. По этой причине, если для val указано значение 0, longjmp() фактически применяет значение 1.
Используемый обеими функциями аргумент env предоставляет связующий элемент, позволяющий осуществить переход. При вызове setjmp() в env сохраняется различная информация о среде текущего процесса. Это позволяет выполнить вызов longjmp(), которому для осуществления фиктивного возвращения нужно указать ту же переменную env. Поскольку вызовы setjmp() и longjmp() — различные функции (в противном случае мы могли бы обойтись и простой инструкцией goto), env объявляется глобально или, что менее распространено, передается в качестве аргумента функции.
На момент вызова setjmp() в env наряду с другой информацией хранятся копии регистра
• Удалить из стека стековые фреймы для всех промежуточных функций между функцией, вызвавшей longjmp(), и функцией, которая перед этим вызвала setjmp(). Эту процедуру иногда называют «раскруткой стека», и она выполняется путем сброса регистра указателя стека и присвоением ему значения, сохраненного в аргументе env.
• Установить такое значение регистра, чтобы выполнение программы продолжалось с места предварительного вызова setjmp(). Это действие также выполняется с использованием значения, сохраненного в env.