Подтвердить что ты не робот

Практическое использование setjmp и longjmp в C

Может ли кто-нибудь объяснить мне, где именно функции setjmp() и longjmp() могут использоваться практически во встроенном программировании? Я знаю, что они предназначены для обработки ошибок. Но я хотел бы знать некоторые варианты использования.

4b9b3361

Ответ 1

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

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

Это ситуация, когда setjmp/longjmp имеет смысл. Эти ситуации похожи на ситуацию, когда смысл в других языках (С++, Java) имеет смысл.

Сопрограммы
Помимо обработки ошибок, я могу думать и о другой ситуации, когда вам нужен setjmp/longjmp в C:

Это тот случай, когда вам нужно реализовать сопрограммы.

Вот небольшой демонстрационный пример. Надеюсь, он удовлетворяет запросу Sivaprasad Palas для примера кода и отвечает на вопрос TheBlastOne, как setjmp/longjmp поддерживает реализацию корректов (насколько я вижу, он не основан на каком-то нестандартном или новом поведении).

EDIT:
Может быть, на самом деле это undefined поведение longjmp вниз по стоп-косту (см. Комментарий MikeMB, хотя я еще не имел возможности проверить это).

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

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration 

void routineA()
{
    int r ;

    printf("(A1)\n");

    r = setjmp(bufferA);
    if (r == 0) routineB();

    printf("(A2) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20001);

    printf("(A3) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20002);

    printf("(A4) r=%d\n",r);
}

void routineB()
{
    int r;

    printf("(B1)\n");

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10001);

    printf("(B2) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10002);

    printf("(B3) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10003);
}


int main(int argc, char **argv) 
{
    routineA();
    return 0;
}

В следующем рисунке показан ход выполнения:
flow of execution

Предупреждающая записка
При использовании setjmp/longjmp следует знать, что они влияют на справедливость локальных переменных, которые часто не учитываются.
Ср my вопрос по этому вопросу.

Ответ 2

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

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

В моем опыте в большинстве случаев всякий раз, когда вы думаете, что использование setjmp/longjmp будет работать, ваша программа понятна и достаточно проста, что каждый вызов промежуточной функции в цепочке вызовов может обрабатывать ошибки, или это так беспорядочно и невозможно устранить, что вы должны сделать exit, когда вы столкнулись с ошибкой.

Ответ 3

Комбинация setjmp и longjmp - это "суперпрочность goto". Используйте с ЭКСТРЕМАЛЬНОЙ заботой. Однако, как объясняли другие, a longjmp очень полезно выйти из неприятной ситуации с ошибкой, когда вы хотите get me back to the beginning быстро, вместо того, чтобы сбрасывать сообщение об ошибке для 18 уровней функций.

Однако, как и goto, но хуже, вы должны быть ДЕЙСТВИТЕЛЬНО осторожны, как вы это используете. A longjmp вернет вас к началу кода. Это не повлияет на все другие состояния, которые могут измениться между setjmp и вернуться к началу setjmp. Таким образом, выделения, блокировки, полуинициализированные структуры данных и т.д. По-прежнему выделяются, блокируются и частично инициализируются, когда вы возвращаетесь туда, где был вызван setjmp. Это означает, что вам действительно нужно позаботиться о местах, где вы это делаете, что ДЕЙСТВИТЕЛЬНО нормально называть longjmp, не вызывая БОЛЬШЕ проблем. Конечно, если следующая вещь, которую вы делаете, это "перезагрузка" [после сохранения сообщения об ошибке, возможно] - во встроенной системе, где вы обнаружили, что оборудование находится в плохом состоянии, например, тогда отлично.

Я также видел, что setjmp/longjmp используется для обеспечения очень простых механизмов потоков. Но этот довольно частный случай - и определенно не то, как работают "стандартные" потоки.

Изменить: можно было бы, конечно, добавить код, чтобы "справиться с очисткой", так же, как С++ хранит точки исключения в скомпилированном коде, а затем знает, что дало исключение и что нужно очистить. Это будет связано с какой-то таблицей указателей функций и сохранением "если мы выпрыгнем снизу сюда, вызовите эту функцию с помощью этого аргумента". Что-то вроде этого:

struct 
{
    void (*destructor)(void *ptr);
};


void LockForceUnlock(void *vlock)
{
   LOCK* lock = vlock;
}


LOCK func_lock;


void func()
{
   ref = add_destructor(LockForceUnlock, mylock);
   Lock(func_lock)
   ... 
   func2();   // May call longjmp. 

   Unlock(func_lock);
   remove_destructor(ref);
}

С помощью этой системы вы можете выполнить "полную обработку исключений, такую ​​как С++". Но это довольно грязно и полагается на хорошо написанный код.

Ответ 4

Поскольку вы упоминаете встроенный, я думаю, что стоит отметить случай неиспользования: когда ваш стандарт кодирования запрещает это. Например, MISRA (MISRA-C: 2004: правило 20.7) и JFS (AV-правило 20): "Макрос setjmp и функция longjmp не должны использоваться".

Ответ 5

setjmp и longjmp могут быть очень полезны при модульном тестировании.

Предположим, что мы хотим протестировать следующий модуль:

#include <stdlib.h>

int my_div(int x, int y)
{
    if (y==0) exit(2);
    return x/y;
}

Обычно, если функция проверки вызывает другую функцию, вы можете объявить функцию-заглушку для ее вызова, которая будет имитировать то, что фактическая функция выполняет для проверки определенных потоков. Однако в этом случае функция вызывает exit, которая не возвращается. Штук должен каким-то образом подражать этому поведению. setjmp и longjmp могут сделать это для вас.

Чтобы проверить эту функцию, мы можем создать следующую тестовую программу:

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

// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))

// the function to test
int my_div(int x, int y);

// main result return code used by redefined assert
static int rslt;

// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;

// test suite main variables
static int done;
static int num_tests;
static int tests_passed;

//  utility function
void TestStart(char *name)
{
    num_tests++;
    rslt = 1;
    printf("-- Testing %s ... ",name);
}

//  utility function
void TestEnd()
{
    if (rslt) tests_passed++;
    printf("%s\n", rslt ? "success" : "fail");
}

// stub function
void exit(int code)
{
    if (!done)
    {
        assert(should_exit==1);
        assert(expected_code==code);
        longjmp(jump_env, 1);
    }
    else
    {
        _exit(code);
    }
}

// test case
void test_normal()
{
    int jmp_rval;
    int r;

    TestStart("test_normal");
    should_exit = 0;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(12,3);
    }

    assert(jmp_rval==0);
    assert(r==4);
    TestEnd();
}

// test case
void test_div0()
{
    int jmp_rval;
    int r;

    TestStart("test_div0");
    should_exit = 1;
    expected_code = 2;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(2,0);
    }

    assert(jmp_rval==1);
    TestEnd();
}

int main()
{
    num_tests = 0;
    tests_passed = 0;
    done = 0;
    test_normal();
    test_div0();
    printf("Total tests passed: %d\n", tests_passed);
    done = 1;
    return !(tests_passed == num_tests);
}

В этом примере вы используете setjmp перед тем, как вводить эту функцию для тестирования, а затем в окоченном exit вы вызываете longjmp, чтобы вернуться обратно в тестовый файл.

Также обратите внимание, что переопределенная exit имеет специальную переменную, которая проверяет, действительно ли вы хотите выйти из программы и вызывает _exit. Если вы этого не сделаете, ваша тестовая программа может не выйти из строя.

Ответ 6

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

Ответ 7

Я написал Java-подобный механизм обработки исключений в C, используя setjmp(), longjmp() и системные функции. Он ловит пользовательские исключения, но также и сигналы, такие как SIGSEGV. Он имеет бесконечное вложение блоков обработки исключений, который работает через вызовы функций и поддерживает две наиболее распространенные реализации потоков. Это позволяет вам определить древовидную иерархию классов исключений, которые имеют наследование во время компоновки, и оператор catch обходит это дерево, чтобы увидеть, нужно ли его перехватить или передать.

Вот пример того, как код выглядит с использованием этого:

try
{
    *((int *)0) = 0;    /* may not be portable */
}
catch (SegmentationFault, e)
{
    long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' };
    ((void(*)())f)();   /* may not be portable */
}
finally
{
    return(1 / strcmp("", ""));
}

И вот часть включаемого файла, который содержит много логики:

#ifndef _EXCEPT_H
#define _EXCEPT_H

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include "Lifo.h"
#include "List.h"

#define SETJMP(env)             sigsetjmp(env, 1)
#define LONGJMP(env, val)       siglongjmp(env, val)
#define JMP_BUF                 sigjmp_buf

typedef void (* Handler)(int);

typedef struct _Class *ClassRef;        /* exception class reference */
struct _Class
{
    int         notRethrown;            /* always 1 (used by throw()) */
    ClassRef    parent;                 /* parent class */
    char *      name;                   /* this class name string */
    int         signalNumber;           /* optional signal number */
};

typedef struct _Class Class[1];         /* exception class */

typedef enum _Scope                     /* exception handling scope */
{
    OUTSIDE = -1,                       /* outside any 'try' */
    INTERNAL,                           /* exception handling internal */
    TRY,                                /* in 'try' (across routine calls) */
    CATCH,                              /* in 'catch' (idem.) */
    FINALLY                             /* in 'finally' (idem.) */
} Scope;

typedef enum _State                     /* exception handling state */
{
    EMPTY,                              /* no exception occurred */
    PENDING,                            /* exception occurred but not caught */
    CAUGHT                              /* occurred exception caught */
} State;

typedef struct _Except                  /* exception handle */
{
    int         notRethrown;            /* always 0 (used by throw()) */
    State       state;                  /* current state of this handle */
    JMP_BUF     throwBuf;               /* start-'catching' destination */
    JMP_BUF     finalBuf;               /* perform-'finally' destination */
    ClassRef    class;                  /* occurred exception class */
    void *      pData;                  /* exception associated (user) data */
    char *      file;                   /* exception file name */
    int         line;                   /* exception line number */
    int         ready;                  /* macro code control flow flag */
    Scope       scope;                  /* exception handling scope */
    int         first;                  /* flag if first try in function */
    List *      checkList;              /* list used by 'catch' checking */
    char*       tryFile;                /* source file name of 'try' */
    int         tryLine;                /* source line number of 'try' */

    ClassRef    (*getClass)(void);      /* method returning class reference */
    char *      (*getMessage)(void);    /* method getting description */
    void *      (*getData)(void);       /* method getting application data */
    void        (*printTryTrace)(FILE*);/* method printing nested trace */
} Except;

typedef struct _Context                 /* exception context per thread */
{
    Except *    pEx;                    /* current exception handle */
    Lifo *      exStack;                /* exception handle stack */
    char        message[1024];          /* used by ExceptGetMessage() */
    Handler     sigAbrtHandler;         /* default SIGABRT handler */
    Handler     sigFpeHandler;          /* default SIGFPE handler */
    Handler     sigIllHandler;          /* default SIGILL handler */
    Handler     sigSegvHandler;         /* default SIGSEGV handler */
    Handler     sigBusHandler;          /* default SIGBUS handler */
} Context;

extern Context *        pC;
extern Class            Throwable;

#define except_class_declare(child, parent) extern Class child
#define except_class_define(child, parent)  Class child = { 1, parent, #child }

except_class_declare(Exception,           Throwable);
except_class_declare(OutOfMemoryError,    Exception);
except_class_declare(FailedAssertion,     Exception);
except_class_declare(RuntimeException,    Exception);
except_class_declare(AbnormalTermination, RuntimeException);  /* SIGABRT */
except_class_declare(ArithmeticException, RuntimeException);  /* SIGFPE */
except_class_declare(IllegalInstruction,  RuntimeException);  /* SIGILL */
except_class_declare(SegmentationFault,   RuntimeException);  /* SIGSEGV */
except_class_declare(BusError,            RuntimeException);  /* SIGBUS */


#ifdef  DEBUG

#define CHECKED                                                         \
        static int checked

#define CHECK_BEGIN(pC, pChecked, file, line)                           \
            ExceptCheckBegin(pC, pChecked, file, line)

#define CHECK(pC, pChecked, class, file, line)                          \
                 ExceptCheck(pC, pChecked, class, file, line)

#define CHECK_END                                                       \
            !checked

#else   /* DEBUG */

#define CHECKED
#define CHECK_BEGIN(pC, pChecked, file, line)           1
#define CHECK(pC, pChecked, class, file, line)          1
#define CHECK_END                                       0

#endif  /* DEBUG */


#define except_thread_cleanup(id)       ExceptThreadCleanup(id)

#define try                                                             \
    ExceptTry(pC, __FILE__, __LINE__);                                  \
    while (1)                                                           \
    {                                                                   \
        Context *       pTmpC = ExceptGetContext(pC);                   \
        Context *       pC = pTmpC;                                     \
        CHECKED;                                                        \
                                                                        \
        if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) &&            \
            pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0)           \
        {                                                               \
            pC->pEx->scope = TRY;                                       \
            do                                                          \
            {

#define catch(class, e)                                                 \
            }                                                           \
            while (0);                                                  \
        }                                                               \
        else if (CHECK(pC, &checked, class, __FILE__, __LINE__) &&      \
                 pC->pEx->ready && ExceptCatch(pC, class))              \
        {                                                               \
            Except *e = LifoPeek(pC->exStack, 1);                       \
            pC->pEx->scope = CATCH;                                     \
            do                                                          \
            {

#define finally                                                         \
            }                                                           \
            while (0);                                                  \
        }                                                               \
        if (CHECK_END)                                                  \
            continue;                                                   \
        if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0)          \
            pC->pEx->ready = 1;                                         \
        else                                                            \
            break;                                                      \
    }                                                                   \
    ExceptGetContext(pC)->pEx->scope = FINALLY;                         \
    while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC))   \
        while (ExceptGetContext(pC)->pEx->ready-- > 0)

#define throw(pExceptOrClass, pData)                                    \
    ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__)

#define return(x)                                                       \
    {                                                                   \
        if (ExceptGetScope(pC) != OUTSIDE)                              \
        {                                                               \
            void *      pData = malloc(sizeof(JMP_BUF));                \
            ExceptGetContext(pC)->pEx->pData = pData;                   \
            if (SETJMP(*(JMP_BUF *)pData) == 0)                         \
                ExceptReturn(pC);                                       \
            else                                                        \
                free(pData);                                            \
        }                                                               \
        return x;                                                       \
    }

#define pending                                                         \
    (ExceptGetContext(pC)->pEx->state == PENDING)

extern Scope    ExceptGetScope(Context *pC);
extern Context *ExceptGetContext(Context *pC);
extern void     ExceptThreadCleanup(int threadId);
extern void     ExceptTry(Context *pC, char *file, int line);
extern void     ExceptThrow(Context *pC, void * pExceptOrClass,
                            void *pData, char *file, int line);
extern int      ExceptCatch(Context *pC, ClassRef class);
extern int      ExceptFinally(Context *pC);
extern void     ExceptReturn(Context *pC);
extern int      ExceptCheckBegin(Context *pC, int *pChecked,
                                 char *file, int line);
extern int      ExceptCheck(Context *pC, int *pChecked, ClassRef class,
                            char *file, int line);


#endif  /* _EXCEPT_H */

Там также модуль C, который содержит логику для обработки сигналов и некоторый учет.

Это было чрезвычайно сложно реализовать, я могу вам сказать, и я почти ушел. Я действительно старался сделать его как можно ближе к Java; Мне было удивительно, как далеко я продвинулся только с C.

Дайте мне крик, если вам интересно.