exit abort return 区别


Exit abort return 三者区别
       exit() 结束当前进程/程序,在整个程序中,只要调用 exit ,就结束。return() 是当前函数返回,当然如果是在主函数main, 自然也就结束当前进程了,如果不是,那就是退回上一层调用。在多个进程时.如果有时要检测某个进程是否正常退出的.就要用到这个进程的返回值。exit(0)表示进程正常退出. 返回 0; exit(1)表示进程非正常退出.返回 1。
       如果是C++的话,还是尽量避免使用exit。用exit 的时候任何自动变量和临时变量的析构函数都不会被调用。如果是C 的话,关系不大。对于这种析构你可以用atexit 来指定一个退出时资源清理。也就是说可以利用atexit 函数为exit 事件"挂接"另外的函数,这种"挂接"有点类似Windows 编程中的"钩子"(Hook)。程序输出"atexit 挂接的函数"后即终止,即便是我们不调用exit 函数,当程序本身退出时,atexit 挂接的函数仍然会被执行。atexit 可以被多次行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行。
       当然,如果不想在出错情况下处理错误信息的话,你可以用abort()来退出(例如在断言中),而不能用exit,否则程序会产生一个系统错误提示,用了ABORT,退出当前处理过程时不会有错误提示出现。
       注意:在多线程程序中,不要在某个线程使用exit abort,这样会导致整个进程退出。
C++ 中exit 的使用
1. 进程的结束
对于一个用C++写的程序,被加载至内存后运行,最终走向死亡。程序的死亡大致有三种:
• 自然死亡,通常就是main()中的一个return 0;
• 自杀,即请求OS 将自己结束。有两种方式:void exit(int status)和void abort(void)。
• 他杀,程序家族中的他杀行径往往是由自己至亲完成的,通常这个至亲就是有亲缘关系的进程。凶器(例如SIGKILL 信号)往往是由OS 直接或者间接提供的。
2. 程序死亡方式对对象析构的影响
       C++程序中大致有三种对象:全局对象、局部静态对象、局部非静态对象(自动对象)。例如:
#include <iostream>
using namespace std;
struct Foo
{
Foo(){ cout<<"Foo"<<endl; }
~Foo(){ cout<<"~Foo"<<endl; }
/* some other sources here */
};
Foo Global;
void quit();
int
main()
{
static Foo StaticLocal;
Foo Local;
// quit();
// abort();
return 0;
}
void quit()
{
Foo AnotherLocal;
exit(1);
}
     编译运行这个程序,程序将正常退出。运行过程中,Global 对象在进入main之前首先被构造,其次是StaticLocal 和Local。main 函数退出之前,Local 和
StaticLocal 被析构,main 退出后Global 也将被析构。
如果将17 行处quit()的注释去掉,我们将会看到4 个对象被构造,但却只有两个对象被析构,分别是Global 和StaticLocal 对象,其他两个对象Local 和AnotherLocal 对象的析构函数将不会被调用。如果将18 行处abort()的注释去掉(quit()被注释),3 个对象对象被构造,但在程序退出之前没有任何一个对象的析构函数被调用。
也就是说,正常情况下,所有类型的对象都将被析构;由exit 退出时只有非自动对象被析构;abort 被调用时,程序将直接退出,任何对象的析构函数都不会调用。
3. exit 做了什么
介绍exit 之前,不得不提void atexit(void (*f)(void) )函数。atexit,描述了exit 里面要做些什么。使用atexit 可以向exit 注册一系列的函数,这些函数在exit 中被调用,调用的顺序与它们被注册的顺序相反。可以使用下面的代码来验证:
#include <iostream>
using namespace std;
void f1(){ cout<<"f1"<<endl; }
void f2(){ cout<<"f2"<<endl; }
int
main()
{
atexit(f1);
atexit(f2);
exit(1);
return 0;
}
       void exit(int status)被调用时,它首先调用全局的或者静态的对象的析构函数,然后调用atexit 所注册的函数。如果这些函数中的某一个再次调用exit,想想会有什么后果?最后,exit 会将代表程序执行状态的status“返回”(确切的说应该叫做传递,因为exit 永远不会返回调用方)给当前程序的父进程。值得一提的是,执行exit 结束程序,虽然自动对象的析构函数不被调用,但当程序结束时,OS 会将该程序占用的资源全部释放。这些资源包括该程序申请的内存(堆)、打开的文件句柄、管道(Unix/Linux)、socket 等。这样一来,似乎那些析构函数不被调用并不会有什么问题。但可惜这只适用於单线程的程序。对于多线程程序来说,只有当整个进程结束时,它占用的资源才会被OS释放,这时某个线程的exit 就有可能带来麻烦(比如内存泄露)。
4. 使用 c++异常来取代exit()函数
使用C++提供的异常机制,可以很好的解决上面的问题。我们可以在需要exit 的地方抛出(throw)异常,然后在捕获(catch)异常处调用exit(或者干脆不用exit),这样,所有需要的析构函数都将被调用。代码:
例一:
#include <iostream>
#include <cstdlib>
using namespace std;
struct Foo
{
Foo(){ cout<<"Foo"<<endl; }
~Foo(){ cout<<"~Foo"<<endl; }
/* some other sources here */
};
struct except: public exception
{
const char* what() const throw()
{
return "except";
}
};
Foo Global;
void quit();
int
main()
{
try
{
static Foo StaticLocal;
Foo Local;
quit();
}
catch(const exception& e)
{
cerr<<e.what()<<endl;
exit(0);
}
return 0;
}
void quit()
{
Foo AnotherLocal;
// exit(1);
throw except();
}
输出:
Foo
Foo
Foo
Foo
~Foo
~Foo
except
~Foo
~Foo
例二:
void main()
{
//初始化
...
try
{
processmail(...);
}
catch (int ret)
{
switch (ret) {
case e_initialization_failure: ...
case e_irrecoverable: ...
...
}
}
}
void processmail(...)
{
//初始化
...
if ( initializationerror )
{
throw(e_initialization_failure);
}
while ( !shutdown ) {
try
{
readmail(...)
}
catch (int ret)
{
switch (ret) {
case e_read_error:
//记录错误信息
...
//试图恢复
...
if ( recovered )
{
continue;
} else {
throw(e_irrecoverable);
}
break;
case ...
}
}
//继续处理
...
}
//throw()可以用来取代缺少的返回码
//但也要注意由此带来的性能损失
throw(s_ok);
} // processmail()
void readmail(...)
{
...
//在此无须捕捉异常
nbytesavailable = readbytes(...)
...
}
int readbytes(...)
{
//读取数据
if ( error )
{
throw(e_read_error);
}
return nbytesread;
}
发布了28 篇原创文章 · 获赞 6 · 访问量 16万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章