为何最后一个catch块结束后仍能引用异常?是否为未定义行为?
关于C++异常对象生命周期与悬垂引用的问题
有结论指出:
当最后一个未通过重抛(即无参throw表达式求值)退出的catch块完成时,异常对象会被销毁。
但以下代码却输出asdf:
#include <iostream> #include <thread> #include <stdexcept> std::exception& get_err() { try { throw std::runtime_error("asdf"); } catch (std::exception& e) { return e; } } int main() { std::thread t; { std::exception& err = get_err(); t = std::thread([&](){ std::this_thread::sleep_for(std::chrono::seconds(1)); // 这难道不是悬垂引用错误吗? std::cout << err.what() << "\n"; }); } t.join(); return 0; }
核心疑问
- 这段代码是否属于未定义行为?为何在catch块结束很久后,仍能使用该异常对象的引用?
- 是左值引用
err延长了对象的生命周期(延续到线程的lambda中)吗?如果生命周期未被延长,代码还能正常运行吗? - 该代码在MSVC 2019 C++20下能运行,其他编译器/标准下的表现如何?
解答
确实是未定义行为
异常对象的生命周期严格遵循“最后一个未重抛的catch块结束时销毁”的规则。get_err()中的catch块在返回引用后就完成了,此时异常对象已经被销毁,err以及lambda中捕获的引用都是悬垂引用。代码能输出asdf只是巧合——销毁后的内存尚未被覆盖,属于“野指针/悬垂引用”的侥幸情况,并非语言标准保证的行为。左值引用无法延长异常对象的生命周期
C++中只有临时对象绑定到const左值引用或右值引用时才会触发生命周期延长,而异常对象不属于临时对象范畴,它是由throw表达式专门创建的特殊对象。因此err作为普通左值引用,无法延长异常对象的生命周期。一旦catch块结束,对象必然销毁,后续使用引用的行为完全是未定义的,可能输出乱码、程序崩溃,或看似正常,完全取决于编译器实现和内存状态。不同编译器/标准下的表现一致
无论是GCC、Clang还是MSVC,无论C11到C20的哪个标准,异常对象的生命周期规则都是统一的。这段代码在所有标准下都属于未定义行为,只是不同编译器的内存布局、优化策略可能导致表现不同:有的编译器可能立即覆盖销毁后的内存,导致程序崩溃或输出乱码;有的可能保留内存内容一段时间,让代码看似正常,但这都不是标准保证的行为。
内容的提问来源于stack exchange,提问作者MHebes
相关产品推荐
相关产品推荐

