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

Pal*_*ala 84 c

任何人都可以解释我究竟在哪里setjmp(),longjmp()功能可以在嵌入式编程中实际使用?我知道这些是用于错误处理的.但我想知道一些用例.

Cur*_*urd 74

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

如果中间的所有函数必须正常返回并且评估返回值或全局错误变量以确定进一步处理没有意义甚至是坏的,那将是非常繁琐和笨拙的.

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

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

当你需要实现协程时就是这种情况.

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

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

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

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

  • 协同程序需要在单独的堆栈上运行,而不是与示例中显示的相同.由于`routineA`和`routineB`使用相同的堆栈,它只适用于非常原始的协同程序.如果`routineA`在第一次调用`routineB`之后调用一个深度嵌套的`routineC`并且这个`routineC`作为coroutine运行`routineB`,那么`routineB`甚至可能破坏`routineC的返回栈(不仅是局部变量) `.所以没有分配一个独占堆栈(通过调用`rountineB`后通过`alloca()`),如果用作配方,你会遇到这个例子的严重问题. (5认同)
  • 请注意,在你的回答中,跳过callstack(从A到B)是未定义的行为). (4认同)
  • 它确实是未定义的。您必须让每个函数在自己独立的堆栈上运行才能优雅地切换上下文 (4认同)
  • 由于setjmp准备,并且longjmp执行跳出当前调用范围回到setjmp范围,如何支持协程的实现?我不知道如何继续执行longjmp出来的例程. (2认同)
  • @TheBlastOne 参见 [维基百科文章](http://en.wikipedia.org/wiki/Setjmp.h)。如果在执行 `longjmp` 之前执行 `setjmp`,你可以继续执行。这是不标准的。 (2认同)
  • 在脚注 248) 中写道:*“例如,通过执行 return 语句或因为另一个 longjmp 调用已导致转移到嵌套调用集合中较早的函数中的 setjmp 调用。”* 因此调用 longjmp 函数从一个函数到更远的一点,调用堆栈也会终止该函数,因此之后跳回它是 UB。 (2认同)
  • 虽然在标准给出的保证下协程确实无法使用“setjmp/longjmp”实现,但[在一些合理的假设下可以完成](https://fanf.livejournal.com/105413)也是事实。 html)关于现实生活中的架构:`setjmp/longjmp`也会盲目地恢复堆栈指针。我们可以分配一个新的堆栈,使用`alloca`或VLA重新定位堆栈指针,然后使用`setjmp/longjmp`在两个堆栈之间切换。 (2认同)

Art*_*Art 17

理论上,您可以使用它们进行错误处理,这样您就可以跳出深层嵌套的调用链,而无需处理链中每个函数的处理错误.

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

根据我的经验,在大多数情况下,只要你认为使用setjmp/ longjmp可以工作,你的程序就足够清晰和简单,以至于调用链中的每个中间函数调用都可以进行错误处理,或者它太乱了,无法解决你应该做的事情exit.遇到错误.

  • 请看`libjpeg`.与在C++中一样,大多数C例程集合都使用`struct*`作为集合操作.它们可以存储在结构中,而不是将中间函数内存分配存储为本地.这允许`longjmp()`处理程序释放内存.此外,这并没有那么多爆破的异常表,所有C++编译器在事后20年仍然生成. (2认同)

Mat*_*son 9

结合setjmp而且longjmp是"超强goto".与EXTREME护理一起使用.但是,正如其他人所解释的那样,longjmp当你想要get me back to the beginning快速,而不是必须为18层函数滴回错误信息时,a 非常有用,可以摆脱令人讨厌的错误情况.

然而,就像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);
}
Run Code Online (Sandbox Code Playgroud)

使用此系统,您可以"像C++一样完成异常处理".但它非常混乱,并依赖于编写良好的代码.


And*_*ler 8

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

  • “C 库中的所有内容都极其重要。” 有一大堆已弃用的东西和从来都不好的东西,比如语言环境。 (4认同)

mea*_*ers 7

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

这是使用此代码的示例:

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("", ""));
}
Run Code Online (Sandbox Code Playgroud)

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

#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 */
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @PaulStelian 而且,[这是你的答案](/sf/ask/3899236421/)“main()”如何在未捕获的异常中退出。请为这个答案点赞:-) (2认同)
  • 看了一眼自述文件,那里很不错。所以基本上它传播到最外面的 try 块并被报告,类似于 JavaScript 的异步函数。好的。稍后我将查看源代码本身。 (2认同)
  • 虽然“SIGSEGV”是可能的,但它很可能是有问题的。很可能有一些东西跑遍了堆栈、堆或静态数据。因此,无论您的处理程序引用什么,都需要小心。您确实需要分配一个最小的环境来仔细设置“SIGSEGV”处理程序,但您所拥有的是一个良好的开始。 (2认同)

Cle*_* J. 6

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


dbu*_*ush 6

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

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

#include <stdlib.h>

int my_div(int x, int y)
{
    if (y==0) exit(2);
    return x/y;
}
Run Code Online (Sandbox Code Playgroud)

通常,如果要测试的功能调用了另一个功能,则可以声明一个存根功能供其调用,该功能将模仿实际功能对某些流量的测试。但是,在这种情况下,该函数调用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);
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,您setjmp在输入要测试的函数之前使用,然后在存根中exit调用longjmp直接返回到测试用例。

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