缘起
在项目中发现某些情况下,对象的析构函数不被调用,比如程序调用exit(), 异常终止等。那么,析构函数什么情况下不会被调用呢?
RAII
RAII(资源获取即初始化RAII, Resource Acquisition Is Initialization)是C++编程中很重要的一项技术。其原理是在对象析构函数中释放该对象获取的资源,利用栈展开过程栈上对象的析构函数将被自动调用的保证,从而正确地释放先前获取的资源。RAII只有在栈展开正常执行的前提下才能正常工作。函数调用和正常的C++异常处理流程(异常处于try-catch块)都存在栈展开。
栈展开
最常见的栈展开就是正常的函数调用,任何一个函数返回都存在栈展开。C++引入异常机制后,当程序抛出异常,在异常向上传递的过程中,其函数调用栈也会展开。
程序终止
先摘录一段来自stackoverflow的回答:
The Standard defines three ways to end execution of a C++ program:
- Return from main. Objects with automatic storage (function-local) have already been destroyed. Objects with static storage (global, class-static, function-static) will be destroyed.
- std::exit from <cstdlib>. Objects with automatic storage are NOT destroyed. Objects with static storage will be destroyed.
- std::abort from <cstdlib>. Objects with automatic and static storage are NOT destroyed.
Also relevant is std::terminate from <exception>. The behavior of terminate can be replaced using std::set_terminate, but terminate must always "terminate execution of the program" by calling abort or some similar implementation-specific alternative. The default is just { std::abort(); }.
C++ will call std::terminate whenever an exception is thrown and C++ can't reasonably do stack unwinding. For example, an exception from a destructor called by stack unwinding or an exception from a static storage object constructor or destructor. In these cases, there is no (more) stack unwinding done.
C++ will also call std::terminate when a matching catch handler is not found. In this single case, C++ may optionally unwind to main before calling terminate. So your example might have different results with a different compiler.(implementation-defined)
So if you use RAII correctly, the remaining steps to "leak-proof" your program are:
- Avoid std::abort.
- Either avoid std::exit or avoid all objects with static storage duration.
- Put a catch (...) handler in main, and make sure no allocations or exceptions happen in or after it.
- Avoid the other programming errors that can cause std::terminate.
- (On some implementations, functions compiled with a C compiler act like they have C++'s empty throw() specification, meaning that exceptions cannot be thrown "past" them even though they have no destructors to be called.)
简单示例:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stdexcept> // for build-in exception, such as runtime_error #include <exception> // c++ header, must use std:: to call function, such as terminate() class Test { public: ~Test() { printf("======== Call ~Test ========\n"); } }; static Test a; int main(int argc, char** argv) { Test t; // exit(-1); // Objects with automatic storage are NOT destroyed. Objects with static storage will be destro yed. // _exit(-1); // Objects with automatic and static storage are NOT destroyed. // abort(); // Objects with automatic and static storage are NOT destroyed. // std::terminate(); // Objects with automatic and static storage are NOT destroyed. // sleep(10); // signal exit(CTRL+C). // Objects with automatic and static storage are NOT destroyed. throw std::runtime_error("test error"); // Like std::terminate() return 0; // Normal. Objects with automatic and static storage are destroyed. }
简单讲就是:除了从main函数返回之外,调用exit(), abort(), terminate()都不保证调用栈正常展开,即RAII将失效。话说回来,程序都终止了,栈不展开关系也不大,RAII失效就失效吧。但是,RAII失效意味着RAII并不能保证资源一定被释放。对于进程生存期的资源,如文件描述符(打开文件,socket等)、内存等,即使RAII失效,进程占用的资源也终将得到释放,但对于内核生存期和文件系统生存期的资源,如IPC(信号量、消息队列、共享内存)、文件等,RAII是存在缺陷的。是否有更加可靠的办法来释放系统资源呢?这个需要进一步探索。
其实,除了上述4种程序的终止方式外,还有下面几种异常的程序终止方式:
1) 系统调用_exit();
2) 非法内存访问;
3) 信号终止;
4) 内存耗尽;
5) (欢迎补充)
总之,当程序异常终止,堆栈是无需展开的,其栈上对象也将不被显示析构。
参考:
相关文章: