Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the setjmp/longjmp crash on MSVC when it didn't in MinGW?

Tags:

c

setjmp

I wanna use setjmp()/longjmp() to implement a coroutine system. Then I decide to code a little .c file to test it. In MinGW, it's OK; I got the result I want. But when I compile it in MSVC++, the program crashes: "access violation"

    #include <stdio.h>
    #include <stdlib.h>
    #include <setjmp.h>

    jmp_buf a;
    int is_invoke=0;

    void 
    action_1()
    {
        for ( ;; ) {
          printf("hello~~~A\n");
          if(!setjmp(a)) {
            is_invoke=1;
            return;
          }  
        }
    }

    void 
    func()
    {
      if (is_invoke) {
        longjmp(a,1);
      }
      action_1();
      printf("end\n");
    }

    void 
    dummy()
    {
      ;
    }

    int 
    main(int argc, char *argv[])
    {
        for ( ;; )  {
          func();
          dummy();
        }
        return 0;
    }
like image 548
TZW Avatar asked Dec 25 '11 03:12

TZW


3 Answers

The man page for setjmp says:

setjmp() saves the stack context/environment in env for later use by longjmp(). The stack context will be invalidated if the function which called setjmp() returns.

In a simple implementation you might suppose that a jmp_buf contains an address to reset the stack pointer to and an address to jump to. As soon as you return from the function which saved the jmp_buf, the stack frame pointed to by the jmp_buf is no longer valid and may immediately become corrupted.

Or in other words, you can only rely on longjmp to act as a sort-of super-return statement - never to go deeper.

I think the reason this works for you in mingw (and for me on Linux) is implementation-specific and possibly down to luck. There is another way - have you read Simon Tatham's evil coroutine macros essay?

like image 195
crazyscot Avatar answered Nov 15 '22 10:11

crazyscot


Since you're invoking undefined behaviour, it's OK for one compiler to crash and another to appear to work. Both are correct - that's the beauty of undefined behaviour.

The trouble is that a saved context - the jmp_buf - only remains valid as long as the function that called setjmp() to set it has not returned.

The C99 standard (no longer the current standard, but this wording is unlikely to have changed significantly) says:

§7.13.2.1 The longjmp function

The longjmp function restores the environment saved by the most recent invocation of the setjmp macro in the same invocation of the program with the corresponding jmp_buf argument. If there has been no such invocation, or if the function containing the invocation of the setjmp macro has terminated execution208) in the interim, or if the invocation of the setjmp macro was within the scope of an identifier with variably modified type and execution has left that scope in the interim, the behavior is undefined.

208) For example, by executing a return statement or because another longjmp call has caused a transfer to a setjmp invocation in a function earlier in the set of nested calls.

Your code is exiting from action_1() almost immediately, rendering the jmp_buf saved by setjmp() worthless.


I created this little demonstration of setjmp() and longjmp() a couple of years ago. It may help you.

/*
@(#)File:           $RCSfile: setjmp.c,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2009/10/01 16:41:04 $
@(#)Purpose:        Demonstrate setjmp() and longjmp()
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 2009
*/


#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>

static jmp_buf target_location;

static void do_something(void)
{
    static int counter = 0;
    if (++counter % 10 == 0)
        printf("---- doing something: %3d\n", counter);
    if (counter % 1000 == 0)
    {
        printf("||-- doing_something: calling longjmp() with value -1\n");
        longjmp(target_location, -1);
    }
}

static void do_something_else(int i, int j)
{
    printf("-->> do_something_else: (%d,%d)\n", i, j);
    do_something();
    if (i > 2 && j > 2 && j % i == 2)
    {
        printf("||-- do_something_else: calling longjmp() with value %d\n", (i + j) % 100);
        longjmp(target_location, (i + j) % 100);
    }
    printf("<<-- do_something_else: (%d,%d)\n", i, j);
}

static void doing_stuff(void)
{
    int i;
    printf("-->> doing_stuff()\n");
    for (i = rand() % 15; i < 30; i++)
    {
        int j;
        do_something();
        for (j = rand() % 10; j < 20; j++)
        {
            do_something_else(i, j);
        }
    }
    printf("<<-- doing_stuff()\n");
}

static void manage_setjmp(void)
{
    printf("-->> manage_setjmp()\n");
    switch (setjmp(target_location))
    {
    case 0:
        /* Initial return - get on with doing stuff */
        doing_stuff();
        break;
    case -1:
        /* Error return - terminate */
        printf("<<-- manage_setjmp() - error return from setjmp()\n");
        return;
    default:
        /* NB: not officially possible to assign the return from setjmp() */
        printf("---- manage_setjmp() - non-error return from setjmp()\n");
        doing_stuff();
        break;
    }
    printf("<<-- manage_setjmp()\n");
}

int main(void)
{
    printf("-->> main()\n");
    manage_setjmp();
    printf("<<-- main()\n");
    return(0);
}
like image 39
Jonathan Leffler Avatar answered Nov 15 '22 10:11

Jonathan Leffler


You cannot use setjmp/longjmp for coroutines. Use makecontext/swapcontext on POSIX, or fibers (CreateFiber, etc.) on windows.

like image 40
zvrba Avatar answered Nov 15 '22 10:11

zvrba