5

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;
    }
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
TZW
  • 51
  • 1
  • 2
  • I read an article awhile ago about how setjmp/longjmp really screwed up the optimizer, and once they got rid of it the program they were working on sped up significantly. – BillRobertson42 Dec 25 '11 at 03:51
  • 1
    Q: What version of MSVC++? Q: Exactly where did it crash? Did you try running in the Visual Studio debugger? Q: Were there any warnings or errors in your MSVC++ compile? – paulsm4 Dec 25 '11 at 03:54
  • my version of MSVC is 2008. the crash in longjmp. yes. there's no error & warning on compile. it's a runtime error – TZW Dec 25 '11 at 04:33
  • possible duplicate of [SetJmp/LongJmp: Why is this throwing a segfault?](http://stackoverflow.com/questions/1381937/setjmp-longjmp-why-is-this-throwing-a-segfault) – crazyscot Dec 25 '11 at 05:02

3 Answers3

9

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?

crazyscot
  • 11,819
  • 2
  • 39
  • 40
  • ok~i check the setjmp's wiki: "Similarly, C99 does not require that longjmp preserve the current stack frame. ",but MSVC doesn't support c99 well---so, is it a reason for my program crash? – TZW Dec 25 '11 at 05:27
  • @TZW: No; the trouble is that you returned from the function which called `setjmp()` before calling `longjmp()`. – Jonathan Leffler Dec 25 '11 at 07:55
  • @Jonathan Leffler: so could you tell me is there a simple way to check a jmp_buf's context is valid or not,sir? – TZW Dec 25 '11 at 12:09
  • There is no simple way to do tell whether a `jmp_buf` is still valid; you have to code your program so that it is valid before you use it. If you were using C++, you could use the constructor and destructor of a class to record the validity, but if you're using C++, you'd be extremely unwise to use `setjmp()` and `longjmp()` at all because they do not ensure that destructors are called. That is also a problem in C (allocated resources are not released), but that was presumably something you took into account when choosing to use them. – Jonathan Leffler Dec 25 '11 at 16:42
  • Quoting from C++98 (what I have on hand), section §18.7 "Other runtime support" says in ¶4: _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._ That severely restricts the use of `setjmp()` and `longjmp()` in C++; AFAIC, it essentially renders them unusable without incurring undefined behaviour. – Jonathan Leffler Dec 25 '11 at 16:44
  • so how i implement my own coroutine ? – TZW Dec 26 '11 at 08:31
  • @TZW Go read the essay I linked to in my answer, that explains very well a method you can use to give the appearance of coroutines in standard C. – crazyscot Dec 31 '11 at 03:39
4

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);
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

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

zvrba
  • 24,186
  • 3
  • 55
  • 65