Почему это использование setjmp / longjmp неопределенного поведения? - PullRequest
3 голосов
/ 11 января 2020

Код

#include <csetjmp>

template <typename Callable>
void create_checkpoint(std::jmp_buf buf, Callable&& callable)
{
    if (setjmp(buf) != 0)
    {
        callable();
    }
}

#include <iostream>

struct announcer {
    int id;
    announcer(int id):
        id{id}
    {
        std::cout << "created announcer with id " << id << '\n';
    }
    ~announcer() {
        std::cout << "destructing announcer with id " << id << '\n'; 
    }
};

void oopsie(std::jmp_buf buf, bool shouldJump)
{
    if (shouldJump)
    {
        // std::cout << "performing jump...\n";
        std::longjmp(buf, 1);
    }
}

void test1() 
{
    std::jmp_buf buf;
    announcer a1{1};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, true);
}

void test2()
{
    std::jmp_buf buf;
    announcer a1{1};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, false);


    announcer a2{2};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, true);
}

int main()
{
    try 
    {
        test1();
    }
    catch (...)
    {}

    try 
    {
        test2();
    }
    catch (...)
    {}
}

Контекст

Мне нужно вызвать библиотеку C, которая сообщает об ошибках через longjmp. Чтобы обеспечить надежную гарантию исключений, я хочу создать функцию, очень похожую на std::lock_guard, например, я просто пишу create_checkpoint(buf, handler) и продолжаю вызывать библиотечные функции C, пока не выделю больше ресурсов (деструкторы для объектов, созданных ниже * 1009). * линии не называются, если я правильно понимаю).

Вопрос

Почему в этом случае вызывается неопределенное поведение и как я могу это исправить?

Как я узнал, что это неопределенное поведение?

Печать сообщения на std::cout до std::longjmp против без печати дает очень разные результаты, даже если эта строка имеет мало общего с потоком управления.

Что мне делать к настоящему времени понимаете?

Я понимаю, что std::longjmp по существу восстанавливает регистры и переходы по указателю инструкции, сохраненному макросом setjmp. Кроме того, функции не оптимизированы вне , и, по крайней мере, во время компиляции, есть инструкция для вызова longjmp.

Преобразование create_checkpoint в макрос , кажется, решает проблему . И все же мне интересно, есть ли лучший способ сделать это?

Ответы [ 2 ]

4 голосов
/ 11 января 2020

С https://en.cppreference.com/w/cpp/utility/program/longjmp

Если вышла функция, вызвавшая setjmp, поведение не определено (другими словами, разрешены только длинные прыжки вверх по стеку вызовов)

Поскольку вы не следуете этому правилу, ваша программа имеет неопределенное поведение

1 голос
/ 14 января 2020

Код, который заполняет jmp_buff, должен знать, что должно быть слева в стеке после его передачи в longjmp. Если setjmp были обработаны как intrinsi c компилятора, который можно было использовать только внутри функции, возвращающей int, компилятор мог бы упорядочить вещи так, чтобы longjmp заставил функцию, вызвавшую setjmp, "вернуть дважды" вместо того, чтобы относиться к самому setjmp как к такому. Однако во многих реализациях вызовы setjmp обрабатываются так же, как и любые другие вызовы функций, о которых знания компилятора ограничены прототипом. В таких реализациях у setjmp не было бы способа организовать возврат longjmp вызывающей функции без информации о кадре стека этой функции. Хотя компилятор, обрабатывающий вызов setjmp, будет иметь необходимую информацию, у него не будет причин делать его доступным для setjmp, а setjmp не сможет получить информацию без такой поддержки компилятора.

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

int setJmpValue;
switch(setjmp(...))
{
  case 0: setJmpValue=0; break;
  case 1: setJmpValue=1; break;
  ...
  case INT_MAX-1: setJmpValue = INT_MAX-1; break;
  case INT_MAX  : setJmpValue = INT_MAX  ; break;
}

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

Я не думаю, что с разрешением * 1023 должны возникнуть какие-либо трудности. * где i - это int длительности stati c или automa c, что, в свою очередь, сделало бы возможным любое произвольное использование возвращаемого значения, но в стандарте такая конструкция не предусмотрена, и компиляторам больше не модно обрабатывать полезные конструкции предсказуемо, за исключением случаев, когда стандарт заставляет их это делать.

...