3

In jpeglib, one has to use setjmp/longjmp to implement custom error handling.

There are lots of resources where it is said that setjmp/longjmp do not play well with c++ (for example answers in this question tell they do go along with RAII), but the answers to this question say that the destructor is called.

I have this example (taken from here and modified a bit):

#include <iostream>
#include <csetjmp>

std::jmp_buf jump_buffer;

struct A
{
    A(){std::cout<<"A()"<<std::endl;}
    ~A(){std::cout<<"~A()"<<std::endl;}
};

void a(int count) 
{
    std::cout << "a(" << count << ") called\n";
    std::longjmp(jump_buffer, count+1);  // setjump() will return count+1
}

int main()
{
    // is this object safely destroyed?
    A obj;

    int count = setjmp(jump_buffer);
    if (count != 9) {
        a(count);
    }
}

In this example, the destructor is called (as I expected), but is it the standard behaviour? Or is it compiler's extension, or simple UB?


The output:

A()
a(0) called
a(1) called
a(2) called
a(3) called
a(4) called
a(5) called
a(6) called
a(7) called
a(8) called
~A()
Community
  • 1
  • 1
BЈовић
  • 62,405
  • 41
  • 173
  • 273

1 Answers1

7

It can be undefined behaviour depending on whether or not destructors would be called were an exception to execute the same transfer of control. In C++03. From section 18.7 Other runtime support, paragraph 4:

The function signature longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. If any automatic objects would be destroyed by a thrown exception transferring control to another (destination) point in the program, then a call to longjmp(jbuf, val) at the throw point that transfers control to the same (destination) point has undefined behavior.

There's similar language in c++11:

The function signature longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects.

However, there appear to be no destructors that are called in transition for this particular piece of code, so I believe it's safe.


Now, if you were to change the code to move the creation to after the setjmp, that becomes undefined behaviour. In my setup (gcc 4.4.5 under Debian), the following code (with everything else identical to your question):

int main() {
    int count = setjmp (jump_buffer);
    A obj;
    if (count != 4) a (count);
}

results in the output:

A()
a(0) called
A()
a(1) called
A()
a(2) called
A()
a(3) called
A()
~A()

and you can see the destructor is not being called as part of the jump although, being undefined, it may be on some systems.


The bottom line is, you shouldn't be jumping from region A to region B where the equivalent throw/catch would properly destruct an object, because there's no guarantee the longjmp will call the destructor.

Actually, there are some who would say you shouldn't use setjmp/longjmp in C++ at all, and I tend to lean that way myself. I have a hard time seeing a need for that even in C.

I think I used it once in my entire career (and that's a long career), to implement co-operative threading in Turbo C under MS-DOS. I can't think of one other time I've ever used it. Not to say there aren't any uses, but they'd be pretty rare.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • But there are no automatic objects that would be destroyed by an exception in this case. – john Apr 29 '13 at 06:38
  • Yes, no exception is thrown. – BЈовић Apr 29 '13 at 06:40
  • 1
    @BЈовић, whether your particular environment works or not is _never_ the decider for UB. Sometimes UB works exactly as you would expect but it's still UB, and may work totally differently in someone else's environment (or during a blue moon, or with different compiler options and so on). For definitive information, the standard is always the controlling document. – paxdiablo Apr 29 '13 at 06:50
  • I understand that if UB works in one environment, might not work in another. btw the object of type A is created on the stack (see the first line of the `main()`. No exception is thrown. Are these paragraphs still relevant? – BЈовић Apr 29 '13 at 07:02
  • Yes, they are relevant, but in the negative sense. If you put a `throw` where you currently have your `longjmp`, and a `catch` where you currently have `setjmp`, no destructors would be called. You'd only have an issue if you'd created `obj` somewhere between the `setjmp` and `longjmp`. – paxdiablo Apr 29 '13 at 07:07
  • But that doesn't really answer the question. All the current standard says is what you quoted, and to see ISO C 7.10.4, 7.8, 7.6, 7.12. So, by the current c++ standard, should the example destruct the object or not? – BЈовић Apr 29 '13 at 07:32
  • 1
    @BЈовић, no it should not, at least as part of the jump. It _will_ be destructed at the end of `main` when it goes out of scope. That's covered in the "normal" part of the standard dealing with local/block scope (3.3.2 of C++03, 3.3.3 of C++11). – paxdiablo Apr 29 '13 at 07:37
  • Ok, that is what I was looking for. And generally, I agree with you on "don't use setjmp/longjmp", but I have to use them because the library is implemented that way. Thanks – BЈовић Apr 29 '13 at 07:54