Обработка исключений в 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++ вызовет деструкторы на всех автоматических объектах между броском и уловка. setjmp/longjmp не будет вызывать деструкторы, так как они не существуют в C. Так что вы сами по себе, насколько вызов free на все, что у вас есть malloced в то же время.

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

#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'D его перед вызовом 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 был использован, но на этот раз, так как он возвращается из прыжка и косвенно называется функцией returns 1.

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


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

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

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

throw говорит почти то же самое, за исключением того, что он более четко и чисто поддерживается в синтаксисе. Также, пока longjmp может принять вас к практически любому место в программе (где бы вы ни делали setjmp), бросок заканчивается непосредственно вверх в иерархии вызовов, где бросок.


чтобы добавить к ответу (и замечанию) Пита Киркхема: поскольку стандарт 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");
  }
}