Обработка исключений в C - Какая польза от setjmp(), возвращающего 0?

У меня есть несколько вопросов, касающихся использования setjmp/longjmp -

  1. Какой смысл использовать setjmp(jmp___buf stackVariables), возвращающий 0. Это значение по умолчанию, на которое мы не можем влиять.

  2. Единственное значение setjmp (stackVariables) заключается в том, чтобы поместить стек в stackVariables. И в основном 0 говорит нам, был ли стек успешно помещен в stack_variables.

  3. Это один из случаев, когда значение ненулевое (любое ненулевое), когда вы возвращаетесь из longjmp. Что возвращается из lomgjmp, когда вы возвращаетесь из longjmp, когда обрабатывается ваше исключение. Это действительно сбивает с толку.

  4. Может кто-нибудь, пожалуйста, связать это, чтобы попытаться / бросить и поймать. И было бы здорово, если бы можно было привести несколько хороших примеров setjmp/longjmp.

  5. Это longJmp, как throw, и вызывается сразу после места, где может быть вызвано исключение.

Благодарю.

4 ответа

Решение

Спецификация C99 дает:

Если возвращение происходит из прямого вызова, макрос setjmp возвращает значение ноль. Если возвращение происходит из вызова функции longjmp, макрос setjmp возвращает ненулевое значение.

Таким образом, ответ на 1 является то, что ноль означает, что вы позвонили setjmp первый раз, и ненулевое значение указывает, что он возвращается из longjmp,

  1. Это подталкивает текущее состояние программы. После longjmp состояние восстанавливается, управление возвращается к точке, в которой оно было вызвано, и возвращаемое значение не равно нулю.

  2. В C. нет исключений. Это похоже на fork возвращает разные значения в зависимости от того, находитесь ли вы в исходном процессе или во втором процессе, который унаследовал среду, если вы знакомы с этим.

  3. try/catch в C++ вызовет деструкторы для всех автоматических объектов между throw и catch. setjmp/longjmp не буду вызывать деструкторы, так как их нет в C. Таким образом, вы сами по себе free на что угодно mallocв то же время

С этой оговоркой, это:

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

void foo ( char** data ) ;
void handle ( char* data ) ;
jmp_buf env;

int main ()
{
    char* data = 0;

    int res = setjmp ( env ); 
    // stored for demo purposes. 
    // in portable code do not store 
    // the result, but test it directly.

    printf ( "setjmp returned %d\n", res );

    if ( res == 0 )
        foo ( &data );
    else
        handle ( data );

    return 0;
}


void foo ( char** data )
{
    *data = malloc ( 32 );

    printf ( "in foo\n" );

    strcpy ( *data, "Hello World" );

    printf ( "data = %s\n", *data );

    longjmp ( env, 42 );
}

void handle ( char* data )
{
    printf ( "in handler\n" );

    if ( data ) {
        free ( data );
        printf ( "data freed\n" );
    }
}

примерно эквивалентно

#include <iostream>

void foo ( ) ;
void handle ( ) ;

int main ()
{
    try {
        foo ();
    } catch (int x) {
        std::cout << "caught " << x << "\n";
        handle ();
    }

    return 0;
}

void foo ( )
{
    printf ( "in foo\n" );

    std::string data = "Hello World";

    std::cout << "data = " << data << "\n";

    throw 42;
}

void handle ( )
{
    std::cout << "in handler\n";
}

В случае с C вы должны сделать явное управление памятью (хотя обычно вы освобождаете ее в функции, которая выполняет malloc, перед вызовом longjmp, поскольку это упрощает жизнь)

setjmp используется для размещения маркера, куда должен возвращаться вызов longjump, он возвращает 0, если вызывается напрямую, возвращает 1, если вызывается, потому что вызывается longjmp для этого setjmp.

Вы должны думать о setjmp как о чем-то, что обычно вызывается и ничего не делает (возвращает 0) в нормальной работе, в то время как возвращает 1, и оно косвенно вызывается (и возвращается оттуда), когда вызывается длинный прыжок. Я знаю, что вы имеете в виду о путанице, потому что это на самом деле запутывает

Вот пример, данный википедией:

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

static jmp_buf buf;

void second(void)
{
    printf("second\n");         // prints
    longjmp(buf,1);             // jumps back to where setjmp was called - making setjmp now return 1
}

void first(void)
{
    second();
    printf("first\n");          // does not print
}

int main()
{   
    if ( ! setjmp(buf) )
    {
        first();                // when executed, setjmp returns 0
    } 
    else
    {                    // when longjmp jumps back, setjmp returns 1
        printf("main");         // prints
    }

    return 0;
}

Вы в состоянии это понять? Когда программа запущена setjmp выполняется в main и возвращает 0 (потому что он вызывается напрямую), поэтому first называется, тот звонит second а потом прибывает longjmp это переключает контекст, возвращаясь туда, где setjmp был использован, но на этот раз, поскольку он возвращается из перехода и косвенно вызывается, функция возвращает 1.

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

Первая часть почти проста: когда вы делаете longjmp, вы попадаете точно после setjmp. Если возвращаемое значение равно 0, это означает, что вы только что сделали setjmp; если он ненулевой, вы знаете, что вы получили от longjmp из другого места. Эта информация часто полезна для контроля того, что ваш код делает после этого.

setjmp / longjmp являются старыми предками throw/catch. setjmp/longjmp определены в C, тогда как throw / catch является более "современным" механизмом для восстановления после ошибок в более объектно-ориентированных языках, таких как C++.

Вызов longjmp говорит: "Я думаю, что здесь что-то не так, помогите, вытащите меня отсюда - я слишком запутался, чтобы привести себя в порядок и вернуться через кучу вызовов функций и если; просто верните меня прямо туда, где мир снова в порядке, сразу после последнего setjmp. "

throw говорит примерно то же самое, за исключением того, что он более четко и четко поддерживается в синтаксисе. Кроме того, хотя longjmp может привести вас практически к ЛЮБОМУ месту в программе (где бы вы ни делали setjmp), throw завершается прямо вверх в иерархии вызовов того, где находится throw.

Просто чтобы добавить к ответу (и замечанию) Пита Киркхема: поскольку стандарт C не позволяет хранить возвращаемое значение setjmp, возможно, пример Пита можно было бы изменить, чтобы использовать вместо него switch. Он по-прежнему демонстрирует, как различать различные возвращаемые значения, но не нарушает стандарт.

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

void foo(char** data) ;
void handle(char* data) ;
jmp_buf env;

int main(void)
{
  char* data = 0;
  switch(setjmp(env))
  {
    case 0:
    {
      printf("setjmp returned 0\n");
      foo(&data);
      break;
    }
    case 42:
    {
      printf("setjmp returned 42\n");
      handle ( data );
      break;
    }
    default:
    {
      printf("setjmp returned something else?\n");
    }
  }
  return 0;
}

void foo(char** data)
{
  *data = malloc(32);
  printf("in foo\n");
  strcpy(*data, "Hello World");
  printf("data = %s\n", *data);
  longjmp(env, 42);
}

void handle(char* data)
{
  printf("in handler\n");
  if(data)
  {
    free(data);
    printf("data freed\n");
  }
}
Другие вопросы по тегам