在C++ 中慎用setjmp和longjmp

前言

setjmp和longjmp 是 C 语言中一个很强大的函数!

setjmplongjmp是C语言中用于实现非局部跳转的函数。它们通常用于处理错误和异常情况,尤其是在C++的异常处理机制不可用或不适用的情况下。

setjmp函数用于保存当前的程序执行环境,包括程序计数器、栈指针、寄存器等信息。这些信息被保存在一个类型为jmp_buf的变量中。setjmp函数的返回值取决于它是如何被调用的。如果是直接调用,它返回0;如果是由longjmp函数调用,它返回longjmp的第二个参数。

#include <setjmp.h>

jmp_buf env;

if (setjmp(env) == 0) {
    
    
    // This is the return from the direct call to setjmp.
    // Do something...
} else {
    
    
    // This is the return from the call to longjmp.
    // Handle the error or exception...
}

longjmp函数用于恢复由setjmp保存的程序执行环境。当调用longjmp时,程序会立即跳转到最近一次调用setjmp的位置,并使setjmp返回longjmp的第二个参数。注意,longjmp不会返回,它会直接改变程序的控制流。

#include <setjmp.h>

jmp_buf env;

void foo() {
    
    
    // An error or exception occurs...
    longjmp(env, 1);
}

int main() {
    
    
    if (setjmp(env) == 0) {
    
    
        foo();
    } else {
    
    
        // Handle the error or exception...
    }

    return 0;
}

虽然setjmplongjmp在某些情况下可能很有用,但它们也有很多潜在的问题。例如,它们不会正确处理C++的对象析构和异常处理机制,可能会导致资源泄露和未定义行为。因此,除非你非常清楚你在做什么,否则最好避免使用setjmplongjmp

longjmp 跳转的资源释放过程

对于C/C++中的基本类型(如int、double等)和在栈上分配的对象,当它们的作用域结束时,它们会自动被销毁,不需要手动释放。这是因为它们的生命周期与它们的作用域绑定。当你离开一个作用域时,该作用域中的所有局部变量都会被自动销毁。

当你使用longjmp进行非局部跳转时,你实际上是在改变程序的控制流,跳出了某些变量的作用域。这意味着这些变量的生命周期结束,它们会被自动销毁。

因此,对于基本类型和在栈上分配的对象,即使你使用longjmp进行非局部跳转,也不会导致内存泄漏,因为它们会在作用域结束时被自动销毁。

然而,对于动态分配的内存和其他需要手动管理的资源(如打开的文件、锁定的互斥锁等),你需要确保在调用longjmp之前正确地释放它们,否则可能会导致资源泄漏。

C++ 使用setjmp和longjmp 的危险性

在C++中,使用setjmplongjmp进行非局部跳转是可能的,但并不推荐。这是因为setjmplongjmp不会正确处理C++的对象析构和异常处理机制,可能会导致资源泄露和未定义行为。

当你调用longjmp时,它会立即跳转到最近的setjmp位置,而不会执行在这两个位置之间的任何代码。这意味着如果你在setjmplongjmp之间创建了任何对象,这些对象的析构函数不会被调用,可能会导致资源泄露。同样,如果你在setjmplongjmp之间抛出了任何异常,这些异常也不会被捕获。

我拥有地址能访问之前的数据么?

当你使用longjmp进行非局部跳转时,你实际上是在改变程序的控制流,跳出了某些变量的作用域。这意味着这些变量的生命周期结束,它们应该被销毁。然而,由于longjmp的工作方式,这些变量的析构函数不会被调用。这可能会导致资源泄漏,如果这些变量是类的对象,并且在它们的析构函数中管理了一些资源(如动态分配的内存)。

然而,即使这些变量的析构函数没有被调用,你仍然不应该再访问这些变量。这是因为它们的生命周期已经结束,它们的状态已经变得未定义。如果你试图访问一个生命周期已经结束的变量,你可能会遇到各种问题,如无效的内存访问错误、未定义的行为等。

因此,即使你在longjmp之后仍然有一个指向对象的指针,你也不应该再使用这个指针,因为该对象的状态已经变得未定义。在C++中,一旦一个对象的生命周期结束,你就应该停止使用该对象。

可以加一个标志位,在跳转后释放所有内存么?

在理论上,你可以在调用longjmp之前设置一个标志位,然后在longjmp之后检查这个标志位,如果它被设置了,那么你就释放所有的资源。然而,在实践中,这可能会非常复杂和容易出错。

首先,你需要确保你能够正确地追踪所有的资源。这可能包括动态分配的内存、打开的文件、锁定的互斥锁、创建的线程等等。在一个大型的程序中,追踪所有的资源可能会非常困难。

其次,你需要确保你能够在任何时候安全地释放这些资源。这可能需要你在程序的每个部分都添加额外的错误处理代码,以处理可能的longjmp。这可能会使你的代码变得非常复杂和难以维护。

最后,即使你能够正确地追踪和释放所有的资源,longjmp仍然可能导致未定义行为。这是因为longjmp不会正确地处理C++的对象析构和异常处理机制。如果你在setjmplongjmp之间创建了任何对象,这些对象的析构函数不会被调用,可能会导致资源泄露。同样,如果你在setjmplongjmp之间抛出了任何异常,这些异常也不会被捕获。

因此,虽然在理论上你可以使用longjmp来处理段错误,但在实践中这通常是一个非常糟糕的主意。如果你的程序发生段错误,最好的做法通常是尽快终止程序,然后使用调试器来找出问题的原因。

全是堆上的对象也会泄漏么?

即使所有的数据都存储在栈上,使用setjmplongjmp进行非局部跳转仍然可能导致问题。这是因为setjmplongjmp不会正确处理C++的对象析构和异常处理机制。

当你调用longjmp时,它会立即跳转到最近的setjmp位置,而不会执行在这两个位置之间的任何代码。这意味着如果你在setjmplongjmp之间创建了任何对象,这些对象的析构函数不会被调用,可能会导致资源泄露。同样,如果你在setjmplongjmp之间抛出了任何异常,这些异常也不会被捕获。

此外,即使你的数据都存储在栈上,你仍然需要确保你能够在任何时候安全地释放这些数据。这可能需要你在程序的每个部分都添加额外的错误处理代码,以处理可能的longjmp。这可能会使你的代码变得非常复杂和难以维护。

因此,虽然在理论上你可以使用longjmp来处理段错误,但在实践中这通常是一个非常糟糕的主意。如果你的程序发生段错误,最好的做法通常是尽快终止程序,然后使用调试器来找出问题的原因。

智能指针能避免longjmp的泄漏么?

智能指针在C++中主要用于自动管理动态分配的内存,以防止内存泄漏。它们并不能直接解决longjmp引发的问题,因为longjmp跳过了智能指针的析构函数,这可能会导致智能指针管理的内存泄漏。

然而,智能指针可以在某些情况下帮助你更安全地管理资源。例如,如果你有一个智能指针,你可以在longjmp之前手动调用其reset方法来释放其管理的内存。这样,即使你跳过了智能指针的析构函数,也不会导致内存泄漏。

然而,这并不能解决所有的问题。例如,如果你在setjmplongjmp之间创建了一个新的智能指针,你可能无法在longjmp之前调用其reset方法,因为你无法预知longjmp的发生。

总的来说,虽然智能指针可以在某些情况下帮助你更安全地管理资源,但它们并不能完全解决longjmp引发的问题。在C++中,最好的做法是避免使用longjmp,并使用异常来进行错误处理。

C++ 中如何安全的使用setjmp和longjmp (如有问题感谢指出)?

1.必须使用堆区内存,栈区对象失去作用域必然会被释放内存,不调用析构函数并不会影响内存的释放.
2.当然你需要一直获取堆区内存的地址,才能在跳转后重新声明一个指针指向它.

猜你喜欢

转载自blog.csdn.net/qq_21438461/article/details/130977952
今日推荐