4

From what I understood, setjmp saves the current context and it's supposed to restore it when calling longjmp. However the next piece of code prints 15 (I compiled with -g and without any optimization). Did I misunderstand this construct or am I missing anything else?

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

Disclaimer: only trying to use it for curiosity. I never heard about this construct until I recently read some paper about NASA coding guidelines that mentioned it's forbidden to use this flow of control construct

Using both c and c++ tags because the code is mixed and I would assume the actual relevant functions are more relevant to c heavy users rather than c++... :/

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
user2717954
  • 1,822
  • 2
  • 17
  • 28
  • ref. [`std::jmp_buf`](https://en.cppreference.com/w/cpp/utility/program/jmp_buf) for what's actually being stored (eg. no local variable values like you appear to think). – Sander De Dycker Oct 22 '19 at 06:40

6 Answers6

7

That's the expected behavior:

Upon return to the scope of setjmp, all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when std::longjmp was executed, except for the non-volatile local variables in setjmp's scope, whose values are indeterminate if they have been changed since the setjmp invocation.

The value of a when executing longjmp is 15, so that is a value one could expect to see (it's indeterminate in general). The jmp_buf only stores the point of execution. Not the state of every variable in the program.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • thanks. how is it different than a simple goto then? – user2717954 Oct 22 '19 at 06:40
  • 2
    @user2717954 - `goto` in standard C and C++ can't cross function boundaries. – StoryTeller - Unslander Monica Oct 22 '19 at 06:41
  • The value of `a` falls in the indeterminate category since it's not volatile and was modified between setjmp and longjmp – Shawn Oct 22 '19 at 07:03
  • @Shawn - One the one had, your are very much correct from a language lawyer point of view. But on the other, this wording is basically saying there is no telling how a called function could modify the value before jumping back. I'm not sure the `longjmp` itself is allowed to modify the values. – StoryTeller - Unslander Monica Oct 22 '19 at 07:09
  • Yeah, in this particular case it's unlikely to change, but in something more complicated it's possible. Odd thing is I *know* I've gotten warnings from gcc about such variables before in my projects but I can't get it or clang to do so with this code. – Shawn Oct 22 '19 at 07:11
  • 1
    Example of something more complicated: Move the `longjmp()` to a different function that does nothing but call it, and now gcc 8 is printing 0 instead of 15 for me. – Shawn Oct 22 '19 at 07:13
  • @Shawn - Okay, I think I see now. That is indeed a good example. Seems GCC is optimizing based on the indeterminate value. I see `a = 15` has been completely removed from the resulting assembly. – StoryTeller - Unslander Monica Oct 22 '19 at 07:21
  • Wait. So, if I use `return` to go up the call stack, all local variables at the call-site stay the same, but if I `longjmp`, all the variables are suddenly indeterminate? Just why? Doesn't this make `longjmp` barely usable? – Aykhan Hagverdili Feb 05 '21 at 12:02
  • @AyxanHaqverdili - Depends. It's not meant to be in common use. As far as "why", IDK. The machineary involved may be limited when the point of return is not localized. Even for "return", if we missmatch calling conventions, the state at the point of return may be a mess. – StoryTeller - Unslander Monica Feb 05 '21 at 13:26
2

The except for the non-volatile local variables in setjmp's scope, whose values are indeterminate if they have been changed since the setjmp invocation. part of the description is really important, because the value you're seeing falls into that indeterminate category.

Consider a slight modification to your program:

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

When I compile and run this version (With -O), I get 0 1 as output, not 15 1 (Because a is indeterminate, your results may vary).

If you want a local variable that's changed between the initial setjmp() call and calling longjmp() to reliably keep that change, it needs to be volatile.

Shawn
  • 47,241
  • 3
  • 26
  • 60
1

I'd just like to answer the other part of the question, by speculating why NASA would forbid these functions (basically linking relevant answers from SO). The use of setjmp and longjmp are discouraged in C++ more so than in C code, due to undefined behavior regarding automatic object destruction, see this SO thread, particularly the comments to the accepted answer:

Generally, whenever there's some way to exit a scope in C++ (return, throw, or whatever), the compiler will place instructions to call the dtors for any automatic variables that need to be destroyed as a result of leaving that block. longjmp() just jumps to a new location in the code, so it will not provide any chance for the dtors to be called. The standard is actually less specific than that - the standard doesn't say that dtors won't be called - it says that all bets are off. You can't depend on any particular behavior in this case.

[...]

Since smart pointers depend on being destroyed, you will get undefined behavior. It's likely that that undefined behavior would include a refcount not getting decremented. You're 'safe' using longjmp() as long as you don't longjmp out of code that should cause dtors to be invoked. However, as David Thornley noted in a comment, setjmp()/longjmp() can be tricky to use right even in straight C - in C++ they're downright dangerous. Avoid them if at all possible.

So what makes the setjmp()/longjmp() tricky in C? Look at possible use cases we can see that one of them is implementation of coroutines. The answer was already given here in the comments @StoryTeler, but could you use goto across different functions?

You can't in Standard C; labels are local to a single function.

The nearest standard equivalent is the setjmp() and longjmp() pair of functions.

However, you're quite limited with setjmp and longjmp as well, and you might quickly run into a segfault. The treasure can again be found in the comments:

You can think of a longjmp() as an "extended return". A successful longjmp() works like a series of successive returns, unwinding the call stack until it reaches the corresponding setjmp(). Once the call stack frames are unwound, they are no longer valid. This is in contrast to implementations of coroutines (eg. Modula-2) or continuations (eg. Scheme) where the call stack remains valid after jumping somewhere else. C and C++ only support a single linear call stack, unless you use threads where you create multiple independent call stacks.

Community
  • 1
  • 1
gstukelj
  • 2,291
  • 1
  • 7
  • 20
0

For setjmp and longjmp the "context" is the execution context, not the actual contents of the stack (where local variables are commonly stored).

Using setjmp and longjmp you can't "roll back" changes made to local variables.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

I think you might be seeing older codebase. Where exception was not quite popular or not available in some compilers.

You should not use setjmp & longjmp until you are working more close to system software.

  • As for the control flow: setjmp returns twice, and longjmp never returns.
  • When you call setjmp for the first time, to store the environment, it returns zero,
  • And then when you call longjmp, the control flow passes to return from setjmp with the value provided in the argument.
  • Use cases are generally cited as “error handling”, and “don’t use these functions”.

setjmp & longjmp stores & restore the CPU SFRs(i.e. context registers).

Here’s a little control flow example:

#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
“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 .

Simple Example

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

This is the reason why setjump() returns 0 for you and as you check for condition,it assigns a=15 and once the process is done,next step it would give 42.

The main feature of these function is to provide a way that deviates from standard call and return sequence. This is mainly used to implement exception handling in C. setjmp can be used like try (in languages like C++ and Java). The call to longjmp can be used like throw (Note that longjmp() transfers control to the point set by setjmp()).