5

我想用 setjmp()/longjmp()来实现一个协程系统。然后我决定编写一个小 .c 文件来测试它。在MinGW中,没关系;我得到了我想要的结果。但是当我在 MSVC++ 中编译它时,程序崩溃了:“访问冲突”

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

3 回答 3

8

setjmp 的手册页说:

setjmp()保存堆栈上下文/环境以env供以后使用 longjmp()setjmp()如果调用的函数返回,堆栈上下文将失效。

在一个简单的实现中,您可能会假设 ajmp_buf包含一个将堆栈指针重置到的地址和一个要跳转到的地址。一旦您从保存 的函数返回,jmp_buf指向的堆栈帧jmp_buf就不再有效,并且可能立即损坏。

或者换句话说,您只能依靠 longjmp 来充当某种超级return语句 - 永远不会更深入。

我认为这在 mingw 中对您有用(在 Linux 上对我有用)的原因是特定于实现的,并且可能取决于运气。还有另一种方式——你读过Simon Tatham 的邪恶协程宏文章吗?

于 2011-12-25T05:00:51.180 回答
4

由于您正在调用未定义的行为,因此一个编译器崩溃而另一个编译器似乎可以工作是可以的。两者都是正确的——这就是未定义行为的美妙之处。

问题在于,jmp_buf只要调用setjmp()设置它的函数没有返回,保存的上下文 - the - 就保持有效。

C99 标准(不再是当前标准,但这种措辞不太可能发生重大变化)说:

§7.13.2.1longjmp功能

该函数使用相应的参数在程序的同一调用中 longjmp恢复最近一次调用宏所保存的环境。如果没有这样的调用,或者如果包含宏调用的函数在中间终止了执行( 208) ,或者如果宏的调用在具有可变修改类型的标识符的范围内并且执行已经离开该在此期间范围内,行为未定义。setjmpjmp_bufsetjmpsetjmp

208)例如,通过执行return语句或因为另一个longjmp调用导致转移到setjmp嵌套调用集中较早的函数中的调用。

您的代码几乎立即退出action_1(),使jmp_buf保存的内容setjmp()一文不值。


几年前setjmp(),我创建了这个小演示。longjmp()它可能会帮助你。

/*
@(#)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);
}
于 2011-12-25T07:42:34.203 回答
1

您不能将 setjmp/longjmp 用于协程。在 POSIX 上使用 makecontext/swapcontext,或者在 windows 上使用光纤(CreateFiber 等)。

于 2011-12-25T07:11:02.503 回答