Почему происходит сбой setjmp / longjmp на MSVC, а на MinGW - нет? - PullRequest
5 голосов
/ 25 декабря 2011

Я хочу использовать 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;
    }

Ответы [ 3 ]

7 голосов
/ 25 декабря 2011

Страница руководства для setjmp гласит:

setjmp() сохраняет контекст / среду стека в env для последующего использования longjmp(). Контекст стека будет признан недействительным, если функция который вызвал setjmp() возвращает.

В простой реализации вы можете предположить, что jmp_buf содержит адрес для сброса указателя стека и адрес для перехода. Как только вы вернетесь из функции, которая сохранила jmp_buf, кадр стека, на который указывает jmp_buf, больше не действителен и может немедленно стать поврежденным.

Или, другими словами, вы можете полагаться только на longjmp, который действует как своего рода супер- return утверждение - никогда не углубляться.

Я думаю, что причина того, что это работает для вас в mingw (и для меня в Linux), связана с реализацией и, возможно, к счастью. Есть другой способ - вы читали злые макросы сопрограммы Саймона Тэтхэма эссе?

4 голосов
/ 25 декабря 2011

Поскольку вы вызываете неопределенное поведение, нормально, что один компилятор аварийно завершит работу, а другой будет работать. И то, и другое правильно - вот в чем прелесть неопределенного поведения.

Проблема в том, что сохраненный контекст - jmp_buf - остается действительным только до тех пор, пока функция, вызвавшая setjmp() для его установки, не вернулась.

Стандарт C99 (более не действующий стандарт, но вряд ли эта формулировка существенно изменилась) гласит:

§7.13.2.1 Функция longjmp

Функция longjmp восстанавливает среду, сохраненную последним вызовом макрос setjmp в том же вызове программы с соответствующим jmp_buf аргумент. Если такого вызова не было, или если функция, содержащая вызов макроса setjmp завершил выполнение 208) в промежуточный период или, если вызов макроса setjmp находился в области идентификатора с переменной измененный тип и выполнение оставили эту область в промежутке, поведение не определено.

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);
}
1 голос
/ 25 декабря 2011

Вы не можете использовать setjmp / longjmp для сопрограмм. Используйте makecontext / swapcontext в POSIX или волокна (CreateFiber и т. Д.) В Windows.

...