为什么 setjmp/longjmp 在 MSVC 上会崩溃,而在 MinGW 中却不会?

发布于 2024-12-22 16:45:35 字数 794 浏览 1 评论 0原文

我想使用 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;
    }

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

时光清浅 2024-12-29 16:45:35

setjmp 的手册页显示:

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

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

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

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

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?

厌倦 2024-12-29 16:45:35

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

问题在于,保存的上下文 - jmp_buf - 只有在调用 setjmp() 来设置它的函数尚未返回时才保持有效。

C99 标准(不再是现行标准,但这种措辞不太可能发生重大变化)表示:

§7.13.2.1 longjmp 函数

longjmp 函数恢复最近调用保存的环境
setjmp 宏在程序的同一调用中具有相应的
jmp_buf 参数。如果没有这样的调用,或者如果函数包含
setjmp 宏的调用已​​暂时终止执行208),或者如果
setjmp 宏的调用位于具有变量的标识符的范围内
修改后的类型和执行期间已离开该范围,行为未定义。

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

您的代码几乎立即从 action_1() 退出,使得 setjmp() 保存的 jmp_buf 毫无价值。


几年前,我创建了这个 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);
}

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);
}
揽清风入怀 2024-12-29 16:45:35

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文