setjmp和longjmp在C语言中的实际应用

bogh5gae  于 2023-02-03  发布在  其他
关注(0)|答案(8)|浏览(135)

有谁能告诉我setjmp()longjmp()函数在嵌入式编程中的实际应用吗?我知道这些是用于错误处理的。但是我想知道一些用例。

xytpbqjk

xytpbqjk1#

    • 错误处理**

假设嵌套在许多其他函数中的函数内部存在一个错误,而错误处理只有在顶级函数中才有意义。
如果中间的所有函数都必须正常返回,并计算返回值或全局错误变量,以确定进一步的处理没有意义,甚至是不好的,这将是非常乏味和笨拙的。
这种情况下setjmp/longjmp是有意义的,这些情况类似于其他语言(C++,Java)中异常的情况。

    • 协同程序**

除了错误处理之外,我还可以想到另一种情况,在C中需要setjmp/longjmp:
当您需要实现coroutines时就是这种情况。
这里有一个小的演示示例,我希望它能满足Sivaprasad Palas对一些示例代码的请求,并回答TheBlastOne的问题setjmp/longjmp如何支持corroutines的实现(就我所见,它并不基于任何非标准或新的行为)。

    • 编辑:**

它可能实际上 * 是 * 未定义的行为来执行longjmp * down * 调用堆栈(参见MikeMB的注解;尽管我还没有机会证实这一点)。

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

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration 

void routineA()
{
    int r ;

    printf("- 12 : (A1)\n");

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

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

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

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

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

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

void routineB()
{
    int r;

    printf("- 34 : (B1)\n");

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

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

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

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

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

int main(int argc, char **argv) 
{
    routineA();
    return 0;
}
    • 产出**
- 12 : (A1)
- 34 : (B1)
- 17 : (A2) r=10001
- 39 : (B2) r=20001
- 22 : (A3) r=10002
- 44 : (B3) r=20002
- 27 : (A4) r=10003

下图显示了执行流程:

    • 警告注解**

在使用setjmp/longjmp时,请注意它们对局部变量的有效性有影响,但通常不会考虑。
参见我的question about this topic

uoifb46i

uoifb46i2#

其原理是,您可以使用它们进行错误处理,这样您就可以跳出深度嵌套的调用链,而无需处理链中每个函数的错误处理。
就像所有聪明的理论一样,当遇到现实时,这个理论就福尔斯了。你的中间函数会分配内存,抓取锁,打开文件,做各种各样需要清理的事情。所以在实践中,setjmp/longjmp通常是个坏主意,除非在非常有限的情况下,你可以完全控制你的环境(一些嵌入式平台)。
根据我的经验,在大多数情况下,当您认为使用setjmp/longjmp可以工作时,您的程序足够清晰和简单,调用链中的每个中间函数调用都可以进行错误处理,或者它太混乱,无法修复,当您遇到错误时,您应该使用exit

eit6fx6z

eit6fx6z3#

我用C语言编写了一个Java-like exception handling mechanism,使用了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("", ""));
}

下面是包含大量逻辑的include文件的一部分:

#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就能走这么远真是令人惊讶。
有兴趣的话就给予我一声。

n3h0vuf2

n3h0vuf24#

setjmplongjmp的组合是“超强的goto“。使用时要格外小心。然而,正如其他人所解释的,当您想要快速地get me back to the beginning时,longjmp对于摆脱讨厌的错误情况非常有用,而不是必须为18层函数涓涓细流地返回错误消息。
然而,就像goto一样,更糟糕的是,你必须非常小心地使用它。longjmp只会让你回到代码的开头。它不会影响在setjmp和回到setjmp开始的地方之间可能已经改变的所有其他状态。所以分配、锁、半初始化的数据结构等仍然是分配的。当你回到调用setjmp的地方时,它被锁定和半初始化。这意味着,你必须真正关心你这样做的地方,调用longjmp而不引起更多的问题是真的可以的。当然,如果你做的下一件事是“重新启动”[在存储了关于错误的消息后,也许] -例如,在嵌入式系统中,您发现硬件处于不良状态,则可以。
我也见过setjmp/longjmp用来提供非常基本的线程机制,但这是非常特殊的情况--而且绝对不是“标准”线程的工作方式。
编辑:当然可以添加代码来“处理清理”,就像C++在编译后的代码中存储异常点,然后知道是什么引起了异常,什么需要清理一样。这将涉及到某种函数指针表,并存储“如果我们从下面这里跳出来,调用这个函数,使用这个参数”。类似于这样:

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

有了这个系统,你可以做“像C++一样完整的异常处理",但它相当混乱,而且依赖于代码的良好编写。

ndh0cuux

ndh0cuux5#

setjmplongjmp在单元测试中非常有用。
假设我们要测试以下模块:

#include <stdlib.h>

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

通常情况下,如果要测试的函数调用另一个函数,您可以声明一个存根函数供其调用,该函数将模拟实际函数测试某些流的操作。然而,在这种情况下,函数调用exit,该函数不会返回。存根需要以某种方式模拟此行为。setjmplongjmp可以为您完成此操作。
要测试此功能,我们可以创建以下测试程序:

#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来退出,如果你不这样做,你的测试程序可能不会干净地退出。

kqqjbcuj

kqqjbcuj6#

既然您提到了嵌入式,我认为值得注意一个非使用情形:例如MISRA(MISRA-C:2004:规则20.7)和JFS(AV规则20):“不应使用setjmp宏和longjmp函数。”

tyu7yeag

tyu7yeag7#

毫无疑问,setjmp/longjmp最关键的用途是它充当“非本地后藤跳转”。(在极少数情况下,您需要在for和while循环上使用后藤)是在同一作用域中使用最安全的。(或跨自动分配),你将很可能损坏你的程序的栈。setjmp/longjmp通过保存栈信息在你想要跳转到的位置来避免这种情况。然后,当你跳转时,它加载堆栈信息。如果没有这个特性,C程序员很可能不得不求助于汇编编程来解决只有setjmp/longjmp才能解决的问题。感谢上帝,它存在。C库中的所有内容都是极其重要的。当你需要它时,你会知道。

js81xvg6

js81xvg68#

除了错误处理之外,你可以做的另一件事是在C语言中以一种聪明的方式实现尾部递归计算。
这实际上是如何在C中实现延续,而不需要以延续传递的方式转换输入代码。

相关问题