user2717954
user2717954

Reputation: 1902

Is longjmp supposed to restore the stack?

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++... :/

Upvotes: 5

Views: 2798

Answers (6)

gstukelj
gstukelj

Reputation: 2551

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.

Upvotes: 1

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.

Upvotes: 8

sivaramakrishnan
sivaramakrishnan

Reputation: 29

“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()).

Upvotes: 0

Shawn
Shawn

Reputation: 52334

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.

Upvotes: 2

Vishal Chovatiya
Vishal Chovatiya

Reputation: 99

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)
    }
}

Upvotes: 0

Some programmer dude
Some programmer dude

Reputation: 409166

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.

Upvotes: 0

Related Questions