setjmp和longjmp在C中的实际用法


96

谁能解释我在嵌入式编程中实际可以使用的确切位置setjmp()longjmp()功能吗?我知道这些用于错误处理。但我想知道一些用例。


像其他任何编程一样进行错误处理。我看不到用法的区别???
托尼狮子


为了速度?是。因为a)它的运行速度比循环慢,并且b)因为它不容易优化(例如删除一个或两个延迟),所以。所以setjmp&longjmp显然是有规律的!
TheBlastOne

除了给出的答案之外,还有另一个答案在这里stackoverflow.com/questions/7334595 / ... 您可能会习惯于longjmp()摆脱信号处理程序的困扰,尤其是像这样的东西BUS ERROR。该信号通常无法重启。嵌入式应用程序可能希望处理这种情况,以确保安全性和鲁棒性。
无声的噪音

关于setjmpBSD和Linux之间的性能差异,请参阅“定时setjmp和标准的喜悦”,建议使用sigsetjmp
Ioannis Filippidis

Answers:


81

错误处理
假设嵌套在许多其他函数中的某个函数中存在一个深入的错误,并且错误处理仅在顶级函数中才有意义。

如果介于两者之间的所有函数必须正常返回并评估返回值或全局错误变量,以确定进一步的处理没有意义甚至是不好的,那将是非常繁琐和尴尬的。

在这种情况下,setjmp / longjmp有意义。这些情况类似于其他语言(C ++,Java)中的例外有意义的情况。

协程
除了错误处理外,我还可以想到另一种需要在C中使用setjmp / longjmp的情况:

当您需要实现协程时就是这种情况。

这是一个演示示例。我希望它能够满足Sivaprasad Palas对某些示例代码的请求,并回答TheBlastOne的问题setjmp / longjmp如何支持例程的实现(就我所知,它不基于任何非标准或新的行为)。

编辑:
可能是降低调用堆栈实际上未定义的行为(请参阅MikeMB的评论;尽管我还没有机会进行验证)。longjmp

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

下图显示了执行流程:
执行流程

警告说明
使用setjmp / longjmp时,请注意它们会影响通常不考虑的局部变量的有效性。
cf. 我对这个话题的问题


2
由于setjmp准备好了,并且longjmp执行了从当前调用范围跳回到setjmp范围的操作,这将如何支持协程的实现?我看不到如何继续执行longjmp列出的例程。
TheBlastOne

2
@TheBlastOne请参阅Wikipedia文章。如果您setjmp在之前,可以继续执行longjmp。这是非标准的。
Potatoswatter

9
协程需要在单独的堆栈上运行,而不是在示例中所示的堆栈上运行。由于routineAroutineB使用相同的堆栈,它仅适用于非常原始的协同程序。如果在第一个调用之后routineA调用了一个深层嵌套routineCroutineB并且routineCroutineB作为协程运行,那么routineB甚至可能破坏的返回堆栈(不仅是局部变量)routineC。因此,如果不分配专用堆栈(通过alloca()调用rountineB?之后),则该示例用作配方时,将遇到严重麻烦。
蒂诺2015年

6
请在您的答案中提及,将调用堆栈跳下(从A到B)是未定义的行为)。
MikeMB

1
并且在脚注248中的内容为:“例如,通过执行return语句,或者由于另一个longjmp调用已导致嵌套调用集中的函数中的setjmp调用转移。” 因此,从函数中调用longjmp函数到调用栈的更远一点也将终止该函数,因此之后又回跳到UB。
MikeMB

18

从理论上讲,您可以将它们用于错误处理,这样您就可以跳出深度嵌套的调用链,而无需处理链中每个函数的错误。

像每一个聪明的理论一样,当遇到现实时,这也就瓦解了。您的中间函数将分配内存,获取锁,打开文件并执行各种需要清除的不同操作。因此,在实践中setjmp/ longjmp通常不是一个好主意,除非在非常有限的情况下您可以完全控制环境(某些嵌入式平台)。

以我的经验,在大多数情况下,只要您认为使用setjmp/ longjmp都能奏效,您的程序就足够清晰和简单,以至于调用链中的每个中间函数调用都可以进行错误处理,或者太杂乱且无法修复,以致您无法在需要exit时进行处理遇到错误。


3
请看libjpeg。与C ++中一样,大多数C例程集合都以a struct *作为集合来对某些事物进行操作。可以将中间功能的内存分配存储为结构,而不必将它们作为本地存储。这允许longjmp()处理程序释放内存。而且,它没有那么多爆炸性的异常表,以至于所有C ++编译器仍然在事实发生20年后生成。
artless的噪音

Like every clever theory this falls apart when meeting reality.确实,临时分配之类的方法很longjmp()棘手,因为您随后必须setjmp()在调用堆栈中多次(对于每个需要在退出之前执行某种清除操作的函数一次,然后需要“重新引发异常”)通过longjmp()查看它最初收到的上下文)。如果在之后修改了这些资源,情况将变得更加糟糕setjmp(),因为您必须声明它们volatile以防止longjmp()破坏它们。
sevko 2015年

10

的组合setjmp,并longjmp为“超级力量goto”。使用时要格外小心。然而,正如其他人所解释的,longjmp是非常有用的,得到一个讨厌的错误情形的,当你想get me back to the beginning快速,而不必淌回一个错误消息18层的功能。

但是,就像goto,但更糟的是,您必须非常小心如何使用它。A 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 ++一样的完整异常处理”。但这很混乱,并且依赖于编写得当的代码。


+1,当然,从理论上讲,您可以通过调用setjmp以保护每次初始化来实现干净的异常处理,例如C ++……并且值得一提的是,将其用于线程化是非标准的。
Potatoswatter

8

既然您提到嵌入式,那么我认为值得注意一个非用例:当您的编码标准禁止它时。例如MISRA(MISRA-C:2004:Rule 20.7)和JFS(AV规则20):“不得使用setjmp宏和longjmp函数。”


8

setjmp并且longjmp可以在单元测试是非常有用的。

假设我们要测试以下模块:

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


6

我写了一个类似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语言走到多远就感到惊讶。

如果您有兴趣,请给我喊。


1
我很惊讶,如果没有实际的编译器支持自定义异常,这是可能的。但是真正有趣的是信号如何转换为异常。
Paul Stelian

我会问一件事:那些永远不会被捕获的异常呢?main()如何退出?
Paul Stelian

1
@PaulStelian而且,这是您main()对未捕获的exex 如何退出的答案。请支持这个答案:-)
含义-

1
@PaulStelian啊,我明白你的意思了。我相信未捕获到的运行时异常又被提出,因此适用了一般的(取决于平台的)答案。未捕获到的自定义异常将被打印并忽略。请参阅自述文件中的Progagation部分,我已将1999年4月的代码发布到了GitHub(请参阅编辑后的答案中的链接)。看一看; 很难破解。很高兴听到您的想法。
含义-

2
简短阅读了README,那里很不错。因此,基本上,它传播到最外层的try块并被报告,类似于JavaScript的异步函数。真好 稍后我将查看源代码本身。
Paul Stelian

0

不言而喻,setjmp / longjmp的最关键用法是它起到了“非本地goto跳转”的作用。Goto命令(在少数情况下,您需要在for和while循环中使用goto over)在同一范围内使用最安全。如果使用goto在范围(或自动分配)之间跳转,则很有可能会破坏程序的堆栈。setjmp / longjmp通过将堆栈信息保存在要跳转到的位置来避免这种情况。然后,当您跳转时,它将加载此堆栈信息。没有此功能,C程序员很可能不得不转向汇编编程来解决只有setjmp / longjmp才能解决的问题。感谢上帝,它的存在。C库中的所有内容都非常重要。您会在需要时知道。


“ C库中的所有内容都非常重要。” 有一大堆不推荐使用的东西和诸如地区设置之类的不好的东西。
qwr
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.