原来退出程序的方法有很多,甚至可以设置“钩子”,让某个函数在程序退出时运行。
本章我们就来深入看看这些方法。
我们已经在退出状态一节里讲过退出状态码的含义,需要的话就翻回去复习一下。
本节中的所有函数都在 <stdlib.h> 中。
我们先从退出程序的常规方式讲起,然后再看一些更少见、更冷门的方式。
正常退出程序时,所有打开的 I/O 流都会被刷新,临时文件也会被删除。基本上,这是一种很友好的退出方式,一切都会被清理和妥善处理。除非你有理由不那么做,否则几乎任何时候都该这么做。
main() 返回你可能注意到了,main() 的返回类型是 int……然而我很少(如果有过的话)从 main() 里 return 任何东西。
这是因为只有 main()(我怎么强调都不为过:这个特例只适用于 main(),不适用于其他任何地方的任何函数)在函数执行到末尾时,会有一个隐式的 return 0。
你随时都可以显式地从 main() 中 return,而且有些程序员觉得,在 main() 末尾始终放一个 return 才更正确。但如果你不写,C 会替你补上一个。
那么……以下是 main() 的 return 规则:
return 语句从 main() 返回一个退出状态。main() 是唯一具有这种特殊行为的函数。在其他任何函数中使用 return,只是从该函数返回到调用者。return,而是直接执行到 main() 的末尾,那就等同于返回了 0 或 EXIT_SUCCESS。exit()这个我们前面也已经碰到过好几次了。如果你在程序中的任何位置调用 exit(),程序就会当场退出。
传给 exit() 的实参就是退出状态。
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!quick_exit() 更快地退出它与普通退出类似,区别在于:
atexit() 的处理函数不会被调用。但有一种注册退出处理函数的办法:像调用 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() 一样,只是可能不会完成文件刷新和清理工作。
_Exit()调用 _Exit() 会立即退出,没有二话。不会执行任何退出时的回调函数。文件不会被刷新。临时文件也不会被删除。
如果你必须现在立刻马上退出,就用它。
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 宏。
abort()如果发生了极其糟糕的事情,而你想向外部环境表明这一点,就可以用它。它也不一定会清理任何已打开的文件等。
我很少见到有人用它。
先为信号埋个伏笔:它实际上是通过引发 SIGABRT 来工作的,而后者会结束进程。
之后会发生什么取决于系统,不过在类 Unix 系统上,程序终止时通常会 转储核心170。