| 目录 |

28 退出程序

原来退出程序的方法有很多,甚至可以设置“钩子”,让某个函数在程序退出时运行。

本章我们就来深入看看这些方法。

我们已经在退出状态一节里讲过退出状态码的含义,需要的话就翻回去复习一下。

本节中的所有函数都在 <stdlib.h> 中。

28.1 正常退出

我们先从退出程序的常规方式讲起,然后再看一些更少见、更冷门的方式。

正常退出程序时,所有打开的 I/O 流都会被刷新,临时文件也会被删除。基本上,这是一种很友好的退出方式,一切都会被清理和妥善处理。除非你有理由不那么做,否则几乎任何时候都该这么做。

28.1.1 从 main() 返回

你可能注意到了,main() 的返回类型是 int……然而我很少(如果有过的话)从 main() 里 return 任何东西。

这是因为只有 main()(我怎么强调都不为过:这个特例只适用于 main(),不适用于其他任何地方的任何函数)在函数执行到末尾时,会有一个隐式的 return 0。

你随时都可以显式地从 main() 中 return,而且有些程序员觉得,在 main() 末尾始终放一个 return 才更正确。但如果你不写,C 会替你补上一个。

那么……以下是 main() 的 return 规则:

28.1.2 exit()

这个我们前面也已经碰到过好几次了。如果你在程序中的任何位置调用 exit(),程序就会当场退出。

传给 exit() 的实参就是退出状态。

28.1.3 用 atexit() 设置退出处理函数

你可以注册一些函数,让它们在程序退出时被调用——无论退出是因为从 main() 返回,还是因为调用了 exit() 函数。

用处理函数的名字调用一次 atexit() 就能完成。你可以注册多个退出处理函数,它们会按注册的逆序被调用。

下面是一个示例:

#include <stdio.h>
#include <stdlib.h>

void on_exit_1(void)
{
    printf("Exit handler 1 called!\n");
}

void on_exit_2(void)
{
    printf("Exit handler 2 called!\n");
}

int main(void)
{
    atexit(on_exit_1);
    atexit(on_exit_2);
    
    printf("About to exit...\n");
}

输出如下:

About to exit...
Exit handler 2 called!
Exit handler 1 called!

28.2 用 quick_exit() 更快地退出

它与普通退出类似,区别在于:

但有一种注册退出处理函数的办法:像调用 atexit() 那样,类似地调用 at_quick_exit()。

#include <stdio.h>
#include <stdlib.h>

void on_quick_exit_1(void)
{
    printf("Quick exit handler 1 called!\n");
}

void on_quick_exit_2(void)
{
    printf("Quick exit handler 2 called!\n");
}

void on_exit(void)
{
    printf("Normal exit--I won't be called!\n");
}

int main(void)
{
    at_quick_exit(on_quick_exit_1);
    at_quick_exit(on_quick_exit_2);

    atexit(on_exit);  // 它不会被调用

    printf("About to quick exit...\n");

    quick_exit(0);
}

这样会得到如下输出:

About to quick exit...
Quick exit handler 2 called!
Quick exit handler 1 called!

它的工作方式与 exit()/atexit() 一样,只是可能不会完成文件刷新和清理工作。

28.3 从轨道上核平:_Exit()

调用 _Exit() 会立即退出,没有二话。不会执行任何退出时的回调函数。文件不会被刷新。临时文件也不会被删除。

如果你必须现在立刻马上退出,就用它。

28.4 有时退出:assert()

assert() 语句用来断言某个条件必须为真,否则程序就会退出。

开发者常用断言来捕获那种“绝不该发生”的错误。

#define PI 3.14159

assert(PI > 3);   // 果然如此,那就继续

与之相对:

goats -= 100;

assert(goats >= 0);  // 山羊数量不能为负

在这种情况下,如果我试着运行它,而 goats 掉到 0 以下,就会发生这样的事:

goat_counter: goat_counter.c:8: main: Assertion `goats >= 0' failed.
Aborted

然后我就被扔回了命令行。

这不太友好,所以只用于用户永远不会看到的地方。而且人们常常会自己编写更容易关掉的 assert 宏。

28.5 异常退出:abort()

如果发生了极其糟糕的事情,而你想向外部环境表明这一点,就可以用它。它也不一定会清理任何已打开的文件等。

我很少见到有人用它。

先为信号埋个伏笔:它实际上是通过引发 SIGABRT 来工作的,而后者会结束进程。

之后会发生什么取决于系统,不过在类 Unix 系统上,程序终止时通常会 转储核心170。


| 目录 |