在C++ 中慎用setjmp和longjmp

简介: 在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.当然你需要一直获取堆区内存的地址,才能在跳转后重新声明一个指针指向它.

目录
相关文章
|
6月前
|
Unix Linux C语言
【C/C++ 跳转函数】setjmp 和 longjmp 函数的巧妙运用: C 语言错误处理实践
【C/C++ 跳转函数】setjmp 和 longjmp 函数的巧妙运用: C 语言错误处理实践
100 0
|
1天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
13 2
|
7天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
33 5
|
14天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
45 4
|
15天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
42 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)