C语言 C语言中setjmp和longjmp的实际使用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14685406/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 05:14:38  来源:igfitidea点击:

Practical usage of setjmp and longjmp in C

c

提问by Pala

Can anyone explain me where exactly setjmp()and longjmp()functions can be used practically in embedded programming? I know that these are for error handling. But I'd like to know some use cases.

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

采纳答案by Curd

Error handling
Suppose there is an error deep down in a function nested in many other functions and error handling makes sense only in the top level function.

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

It would be very tedious and awkward if all the functions in between had to return normally and evaluate return values or a global error variable to determine that further processing doesn't make sense or even would be bad.

如果中间的所有函数都必须正常返回并评估返回值或全局错误变量以确定进一步处理没有意义甚至会很糟糕,那将是非常乏味和尴尬的。

That's a situation where setjmp/longjmp makes sense. Those situations are similar to situation where exception in other langages (C++, Java) make sense.

这是 setjmp/longjmp 有意义的情况。这些情况类似于其他语言(C++、Java)中的异常有意义的情况。

Coroutines
Besides error handling, I can think also of another situation where you need setjmp/longjmp in C:

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

It is the case when you need to implement coroutines.

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

Here is a little demo example. I hope it satisfies the request from Sivaprasad Palas for some example code and answers the question of TheBlastOne how setjmp/longjmp supports the implementation of corroutines (as much as I see it doesn't base on any non-standard or new behaviour).

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

EDIT:
It could be that it actually isundefined behaviour to do a longjmpdownthe callstack (see comment of MikeMB; though I have not yet had opportunity to verify that).

编辑:
这可能是因为它实际上不确定的行为做了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;
}

Following figure shows the flow of execution:
flow of execution

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

Warning note
When using setjmp/longjmp be aware that they have an effect on the validity of local variables often not considered.
Cf. my question about this topic.

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

回答by Art

The theory is that you can use them for error handling so that you can jump out of deeply nested call chain without needing to deal with handling errors in every function in the chain.

理论是你可以使用它们进行错误处理,这样你就可以跳出深度嵌套的调用链,而无需在链中的每个函数中处理错误。

Like every clever theory this falls apart when meeting reality. Your intermediate functions will allocate memory, grab locks, open files and do all kinds of different things that require cleanup. So in practice setjmp/longjmpare usually a bad idea except in very limited circumstances where you have total control over your environment (some embedded platforms).

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

In my experience in most cases whenever you think that using setjmp/longjmpwould work, your program is clear and simple enough that every intermediate function call in the call chain can do error handling, or it's so messy and impossible to fix that you should do exitwhen you encounter the error.

根据我的经验,在大多数情况下,只要您认为使用setjmp/longjmp会起作用,您的程序就足够清晰和简单,以至于调用链中的每个中间函数调用都可以进行错误处理,或者它是如此混乱且无法修复,因此您应该exit在遇到错误。

回答by Mats Petersson

The combination of setjmpand longjmpis "super strength goto". Use with EXTREME care. However, as others have explained, a longjmpis very useful to get out of a nasty error situation, when you want to get me back to the beginningquickly, rather than having to trickle back an error message for 18 layers of functions.

的组合setjmp,并longjmp为“超级力量goto”。使用时要格外小心。但是,正如其他人所解释的那样,longjmp当您想要get me back to the beginning快速摆脱令人讨厌的错误情况时, a非常有用,而不必为 18 层函数返回一条错误消息。

However, just like goto, but worse, you have to be REALLY careful how you use this. A longjmpwill just get you back to the beginning of the code. It won't affect all the other states that may have changed between the setjmpand getting back to where setjmpstarted. So allocations, locks, half-initialized data structures, etc, are still allocated, locked and half-initialized when you get back to where setjmpwas called. This means, you have to really care for the places where you do this, that it's REALLY ok to call longjmpwithout causing MORE problems. Of course, if the next thing you do is "reboot" [after storing a message about the error, perhaps] - in an embedded system where you've discovered that the hardware is in a bad state, for example, then fine.

然而,就像goto,但更糟糕的是,你必须非常小心你如何使用它。Alongjmp只会让您回到代码的开头。它不会影响所有其他可能setjmpsetjmp开始和回到开始之间发生变化的状态。因此,当您返回到setjmp调用的位置时,分配、锁定、半初始化数据结构等仍会被分配、锁定和半初始化。这意味着,你必须真正关心你这样做的地方,在longjmp不引起更多问题的情况下打电话真的没问题。当然,如果您接下来要做的是“重新启动”[在存储有关错误的消息之后,也许] - 例如,在您发现硬件处于不良状态的嵌入式系统中,那很好。

I have also seen setjmp/longjmpused to provide very basic threading mechanisms. But that's pretty special case - and definitely not how "standard" threads work.

我还看到setjmp/longjmp用于提供非常基本的线程机制。但这是非常特殊的情况 - 绝对不是“标准”线程的工作方式。

Edit: One could of course add code to "deal with cleaning up", in the same way that C++ stores the exception points in the compiled code and then knows what gave an exception and what needs cleaning up. This would involve some sort of function pointer table and storing away "if we jump out from below here, call this function, with this argument". Something like this:

编辑:当然可以添加代码来“处理清理”,就像 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);
}

With this system, you could do "complete exception handling like C++". But it's quite messy, and relies on the code being well written.

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

回答by dbush

setjmpand longjmpcan be very useful in unit testing.

setjmp并且longjmp在单元测试中非常有用。

Suppose we want to test the following module:

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

#include <stdlib.h>

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

Normally, if the function to test calls another function, you can declare a stub function for it to call that will mimic what the actual function does to test certain flows. In this case however, the function calls exitwhich does not return. The stub needs to somehow emulate this behavior. setjmpand longjmpcan do that for you.

通常,如果要测试的函数调用另一个函数,您可以声明一个存根函数供它调用,该函数将模拟实际函数为测试某些流所做的工作。然而,在这种情况下,函数调用exit不会返回。存根需要以某种方式模拟这种行为。 setjmp并且longjmp可以为您做到这一点。

To test this function, we can create the following test program:

为了测试这个功能,我们可以创建以下测试程序:

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

In this example, you use setjmpbefore entering the function to test, then in the stubbed exityou call longjmpto return directly back to your test case.

在这个例子中,你setjmp在输入函数之前使用进行测试,然后在exit你调用的存根函数中longjmp直接返回你的测试用例。

Also note that the redefined exithas a special variable that it checks to see if you actually want to exit the program and calls _exitto do so. If you don't do this, your test program may not quit cleanly.

还要注意,重新定义的exit有一个特殊变量,它会检查您是否真的想退出程序并调用_exit它。如果您不这样做,您的测试程序可能无法完全退出。

回答by Clement J.

Since you mention embedded, I think it's worth noting a non-use case: when your coding standard prohibit it. For instance MISRA (MISRA-C:2004:Rule 20.7) and JFS(AV Rule 20) : "The setjmp macro and the longjmp function shall not be used."

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

回答by meaning-matters

I've written a Java-like exception handling mechanismin C using setjmp(), longjmp()and system functions. It catches custom exceptions but also signals like SIGSEGV. It features infinite nesting of exception handling blocks, which works accross function calls, and supports the two most common threading implementations. It allows you to define a tree hierarchy of exception classes that feature link-time inheritance, and the catchstatement walks this tree to see if it needs to catch or pass on.

我已经使用,和系统函数在 C 中编写了一个类似于 Java 的异常处理机制。它捕获自定义异常,但也捕获像. 它具有异常处理块的无限嵌套,可以跨函数调用工作,并支持两种最常见的线程实现。它允许您定义具有链接时继承特性的异常类的树层次结构,并且该语句遍历该树以查看它是否需要捕获或传递。setjmp()longjmp()SIGSEGVcatch

Here's a sample of how code looks using this:

这是使用此代码的示例:

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("", ""));
}

And here's part of the include file that contains a lot of logic:

这是包含大量逻辑的包含文件的一部分:

#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 */

There's also a C module that contains the logic for signal handling and some bookkeeping.

还有一个 C 模块,其中包含信号处理和一些簿记的逻辑。

It was extremely tricky to implement I can tell you and I almost quit. I really pushed to make it as close to Java as possible; I found it surprising how far I got with just C.

我可以告诉你,实施起来非常棘手,我几乎要退出了。我真的努力让它尽可能接近 Java;我发现仅使用 C 语言就能走多远令人惊讶。

Give me a shout if you're interested.

如果你有兴趣,给我一个呼喊。

回答by AndreGraveler

Hands down, the most crucial use of setjmp/longjmp is that it acts a "non-local goto jump". Goto command (and there rare instances where you will need to use goto over for and while loops) is most-used-safely in the same scope. If you use goto to jump across scopes (or across auto allocation), you will most-likely corrupt your program's stack. setjmp/longjmp avoids this by saving the stack info at the location you want to jump to. Then, when you jump, it loads this stack info. Without this feature, C programmers would most likely had to turn to assembly programming to solve issues that only setjmp/longjmp could solve. Thank God it exists. Everything in the C library is extremely important. You will know when you need it.

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

回答by Venkat S A

setjmp() and longjmp() are useful for dealing with errors and interrupts encountered in a low-level subroutine of a program.

setjmp() 和 longjmp() 可用于处理程序的低级子例程中遇到的错误和中断。