Longjmp должен восстановить стек? - PullRequest
2 голосов
/ 22 октября 2019

Из того, что я понял, setjmp сохраняет текущий контекст и восстанавливает его при вызове longjmp. Однако следующий фрагмент кода печатает 15 (я скомпилировал с -g и без какой-либо оптимизации). Я неправильно понял эту конструкцию или я что-то упустил?

#include <iostream>
#include <csetjmp>


std::jmp_buf jump_buffer;

int main()
{
    int a = 0;
    if (setjmp(jump_buffer) == 0) {
      a = 15;
      std::longjmp(jump_buffer, 42);
    }
    std::cerr << a << std::endl;
}

Отказ от ответственности: только пытаясь использовать его для любопытства. Я никогда не слышал об этой конструкции, пока недавно не прочитал статью о руководящих принципах кодирования НАСА, в которой упоминалось, что запрещено использовать этот поток управляющей конструкции

Использование тегов c и c ++, потому что код является смешанным, и я предположил бы фактическоесоответствующие функции более актуальны для опытных пользователей, а не для c ++ ...: /

Ответы [ 6 ]

5 голосов
/ 22 октября 2019

Это ожидаемое поведение :

После возврата в область действия setjmp все доступные объекты, флаги состояния с плавающей запятой и другие компоненты абстрактной машины имеютте же значения, что и при выполнении std::longjmp, за исключением энергонезависимых локальных переменных в области действия setjmp, значения которых не определены, если они были изменены после вызова setjmp .

Значение a при выполнении longjmp равно 15, так что это значение, которое можно ожидать (оно вообще не определено). jmp_buf сохраняет только точку исполнения . Не состояние каждой переменной в программе.

2 голосов
/ 22 октября 2019

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

Рассмотрим небольшую модификацию вашей программы:

#include <iostream>
#include <csetjmp>

std::jmp_buf jump_buffer;

void func() {
  std::longjmp(jump_buffer, 42);
}

int main()
{
  int a = 0;
  volatile int b = 0;
  if (std::setjmp(jump_buffer) == 0) {
    a = 15;
    b = 1;
    func();
  }
  std::cout << a << ' ' << b << '\n';
}

Когда я компилирую и запускаю эту версию (с -O), я получаю 0 1 каквывод, а не 15 1 (поскольку a является неопределенным, ваши результаты могут отличаться).

Если вы хотите, чтобы локальная переменная изменялась между начальным вызовом setjmp() и вызовом longjmp(), чтобы надежно сохранить этоизменить, это должно быть volatile.

0 голосов
/ 22 октября 2019

Я просто хотел бы ответить на другую часть вопроса, размышляя, почему НАСА запретило бы эти функции (в основном связывая соответствующие ответы от SO). Использование setjmp и longjmp не рекомендуется в C ++ больше, чем в коде C, из-за неопределенного поведения в отношении автоматического уничтожения объекта , см. Этот поток SO , особенно комментариина принятый ответ:

Как правило, всякий раз, когда есть какой-либо способ выхода из области видимости в C ++ (return, throw или любой другой), компилятор помещает инструкции для вызова dtors для любых автоматических переменных, которые нуждаютсябыть уничтоженным в результате выхода из этого блока. longjmp() просто переходит на новое место в коде, поэтому он не даст никакой возможности для вызова dtors. Стандарт на самом деле менее конкретен - стандарт не говорит, что dtors не будут вызываться - он говорит, что все ставки отменены. Вы не можете зависеть от какого-либо конкретного поведения в этом случае.

[...]

Поскольку умные указатели зависят от уничтожения, вы получите неопределенное поведение. Вполне вероятно, что это неопределенное поведение будет включать в себя пересчет не уменьшается. Вы «в безопасности», используя longjmp() до тех пор, пока у вас нет длинного кода, который должен вызывать вызов dtors. Тем не менее, как отметил Дэвид Торнли в комментарии, setjmp() / longjmp() может быть сложно использовать правильно даже в прямом C - в C ++ они совершенно опасны. Избегайте их, если это вообще возможно.

Так что же делает setjmp() / longjmp() хитрым в C? Посмотрите на возможные варианты использования , мы видим, что одним из них является реализация сопрограмм. Ответ уже был дан здесь в комментариях @StoryTeler, но не могли бы вы использовать goto для различных функций ?

Вы не можете в Standard C;метки являются локальными для одной функции.

Ближайшим стандартным эквивалентом является пара функций setjmp () и longjmp ().

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

Вы можете думать о longjmp() как о «расширенном возвращении». Успешный longjmp() работает как серия последовательных возвратов, раскручивая стек вызовов, пока не достигнет соответствующего setjmp(). Как только кадры стека вызовов развернуты, они больше не действительны. Это в отличие от реализаций сопрограмм (например, Modula-2) или продолжений (например, Схема), где стек вызовов остается действительным после перехода в другое место. C и C ++ поддерживают только один линейный стек вызовов, , если только не использует потоки, в которых вы создаете несколько независимых стеков вызовов.

0 голосов
/ 22 октября 2019
“Setjump” and “Longjump” are defined in setjmp.h, a header file in C standard library.

setjump(jmp_buf buf) : uses buf to remember current position and returns 0.
longjump(jmp_buf buf, i) : Go back to place buf is pointing to and return i .

Простой пример

 #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;
}

Это причина, почему setjump () возвращает 0 для вас, и когда вы проверяете условие, оно присваивает = 15, и как только процесс завершен, следующий шагэто даст 42.

Основная особенность этих функций состоит в том, чтобы обеспечить способ, который отличается от стандартного вызова и последовательности возврата. Это в основном используется для реализации обработки исключений в C. setjmp можно использовать как try (в таких языках, как C ++ и Java). Вызов longjmp может использоваться как throw (обратите внимание, что longjmp () передает управление в точку, установленную setjmp ()).

0 голосов
/ 22 октября 2019

Я думаю, вы могли видеть более старую кодовую базу. Где исключения были не совсем популярны или недоступны в некоторых компиляторах.

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

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

setjmp & longjmp хранит и восстанавливает SFR процессора (то есть регистры контекста).

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

#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void foo()
{
    longjmp(&env, 10);                      +---->----+
}                                           |         |
                                            |         |
int main()              (entry)---+         ^         V
{                                 |         |         |
    if(setjmp(&env) == 0)         | (= 0)   |         | (= 10)
    {                             |         ^         |
        foo();                    +---->----+         |
    }                                                 +---->----+
    else                                                        |
    {                                                           |
        return 0;                                               +--- (end)
    }
}
0 голосов
/ 22 октября 2019

Для setjmp и longjmp "контекст" - это контекст исполнения , а не фактическое содержимое стека (где обычно хранятся локальные переменные).

Использование setjmp и longjmp вы не можете "откатить" изменения, внесенные в локальные переменные.

...