[C++]exit & abort & 析构 & 异常

进程之死

  对于一个用C++写的程序,被加载至内存后运行,最终走向死亡。程序的死亡大致有三种:

自然死亡,即无疾而终,通常就是main()中的一个return 0;

自杀,当程序发现自己再活下去已经没有任何意义时,通常会选择自杀。当然,这种自杀也是一种请求式的自杀,即请求OS将自己毙掉。有两种方式:void exit(int status)和void abort(void)。

他杀,同现实不同的是,程序家族中的他杀行径往往是由自己至亲完成的,通常这个至亲就是他的生身父亲(还是母亲?)。C++并没有提供他杀的凶器,这些凶器往往是由OS直接或者间接(通过一些进程库,如pthread)提供的。

  自然死是最完美的结局,他杀是我们最不愿意看到的,自杀虽是迫不得已,但主动权毕竟还是由程序自己掌控的。下面探究程序一下不同的死亡方式对对象的析构有何影响。

程序死亡方式对对象析构的影响

  C++程序中大致有三种对象:全局对象、局部静态对象、局部非静态对象(自动对象)。举例说明之:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

#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被调用时,程序将直接退出,任何对象的析构函数都不会调用。下面着重介绍下exit的行为。

exit做了什么

  介绍exit之前,不得不提void atexit(void (*f)(void) )函数。atexit,顾名思义,它描述了exit里面要做些什么。可以看出,它接受一个void f(void)形式的函数的指针。使用atexit我们可以向exit注册一些列的函数,这些函数在exit中被调用,调用的顺序与它们被注册的顺序相反。你可以使用下面的代码来验证:

1

2

3

4

5

6

7

8

9

10

11

12

13

#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,your nightmare is coming。最后,exit会将代表程序执行状态的status“返回”(确切的说应该叫做传递,因为exit永远不会返回调用方)给当前程序的父进程。
  值得一提的是,执行exit结束程序,虽然自动对象的析构函数不被调用,但当程序结束时,OS会将该程序占用的资源全部释放。这些资源包括该程序申请的内存(堆)、打开的文件句柄、管道(Unix/Linux)、socket等。这样一来,似乎那些析构函数不被调用并不会有什么问题。确实,但可惜这只适用于单线程的程序。对于多线程程序来说,只有当整个进程结束时,它占用的资源才会被OS释放,这时某个线程的exit就可能带来麻烦(比如内存泄露)。怎么办呢?

使用异常

  使用C++提供的异常机制,可以很好的解决上面提出的问题。我们可以在需要exit的地方抛出(throw)异常,然后在捕获(catch)异常处调用exit,这样,所有需要的析构函数都将被调用。代码通常是这个样子滴:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

#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

补充:还有一个与atexit()相似的函数叫on_exit(),Google之。

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值