setjmp and longjmp in C language

Summary

This article describes the function and principle of setjmp and longjmp functions in C language, the purpose is to lay the foundation for learning the principle of SRS coroutine.

exception handling

We know that in the C++ language, we can use the try catch mechanism to catch exceptions in functions, and then suddenly jump out from the normal code execution flow to the exception handling code branch described by the catch keyword. In the C language, there is no built-in exception capture mechanism of the C++ language, how to achieve similar functions? There are two methods, one is to use the exception handling mechanism provided by the operating system, but this destroys the portability of C code, and another method is to use the setjmp and longjmp functions to simulate a mechanism similar to exception handling.

sample code

Let's first look at a sample code and compare how C and C++ handle a division by 0 exception.
This is a C++ sample project that only contains a single main.cpp file.

#include <cstdio>
#include <cstdlib>
#include <csetjmp>

jmp_buf g_execution_point_context;

// 用C方式处理异常
int c_safe_div(int a, int b) {
    if (b == 0) {
        printf("[C] do not allow division by 0\n");
        // 将setjmp的返回值设置为1
        longjmp(g_execution_point_context, 1);  // 直接跳转到setjmp调用的下一行
        return 0; // 此行代码永远不会被执行
    } else {
        return a / b;
    }
}

void demo_c_setjmp_longjmp() {
    int ret = setjmp(g_execution_point_context);
    if (ret == 0) {
        printf("return from setjmp\n");
        // c_safe_div发现异常后,longjmp到setjmp保存的执行点,再次执行if判断。
        c_safe_div(10, 0);
    } else {
        printf("return from longjmp: %d\n", ret);
    }
}

// 用C++方式处理异常
int cpp_safe_div(int a, int b) {
    if (b == 0) {
        printf("[C++] do not allow division by 0\n");
        // 抛出值为1的异常
        throw 1; // 直接跳转到catch分支内。
        return 0; // 此行代码永远不会被执行。
    } else {
        return a / b;
    }
}

void demo_cpp_exception() {
    try {
        // 可抛出异常的代码块,抛出异常后,由catch语句列表处理。
        cpp_safe_div(10, 0);
    } catch (int exception_code) {
        printf("catched exception code: %d\n", exception_code);
    }
}

int main()
{
    demo_c_setjmp_longjmp();
    demo_cpp_exception();
    return 0;
}

 demo_c_setjmp_longjmp function analysis:
In a function call, both branches of an if else statement are executed, isn't it a bit unintuitive?
This is because setjmp saves the execution point context of a thread. When the divisor is 0, it calls longjmp to restore the execution point context saved at that time, and continues to execute the if judgment after that execution point.
Comparing the setjmp/longjmp mechanism of C and the try catch mechanism of C++, do you feel that the exception handling mechanism of C++ is more intuitive.

execution point context

This article refers to the address of a machine instruction that has not yet been executed but is about to be executed as the execution point (a breakpoint can be set at this address). In the above code, a global variable g_execution_point_context of jmp_buf structure type is used to save the execution point context of the thread. This global variable name is created by the author. It means execution point context in Chinese, and it can also be understood as the execution point environment. So what is execution point context?
Simply put, it is the value of some registers in the CPU. These values ​​describe the state of the thread before executing a certain instruction. The CPU of the x86 architecture contains the following registers:

  • esp: Save the address of the current top of the stack
  • ebp: Save the address of the current function stack frame. At the entry point of the function, save esp to ebp, so that at any position in the function, you can get the parameters of the function by adding offset to ebp.
  • eip: Save the address of the next instruction.
  • eflags: Save various flag bits when the CPU executes instructions, such as overflow flag, carry flag, sign flag, zero flag, single-step flag, etc. .
  • eax: Usually used to save the return value of integer type.
  • ecx: Usually used to save the this pointer of C++, or as a counter for loop instructions.

In fact, there are many function registers of x86 CPU, but setjmp is only responsible for saving the values ​​of a few registers.
In the same thread, with the mechanism of saving and restoring the execution point context, we can freely switch between different execution points through longjmp without worrying about losing the original execution point context. This is the basic principle of coroutines.

Summarize

setjmp and longjmp are respectively used to save and restore the execution point context of the same thread. This feature can be used to simulate C++ exception handling, and can also be used to implement coroutine functions.

Guess you like

Origin blog.csdn.net/bigwave2000/article/details/132215805