实时/非批处理应用中的C退出码和atexit()

3

我正在编写一个游戏,在其中许多不同的条件可能导致失败,例如图像或着色器加载失败,OpenGL无法获得有效上下文等。

在理想情况下,我希望使用退出代码来表示C语言中预期的非零失败条件。然而,有一些因素需要考虑:

  • 使用非零退出代码时,atexit()不会被调用,这对我来说很不幸,因为我的程序清理是通过单个在main()中注册的atexit()处理的。
  • 由于游戏具有不确定的运行时间,与批处理程序不同,实际上可能永远不会出现使用退出代码的实际情况。这并不像由Makefile调用的命令行图像转换器那样需要知道程序是否成功或失败,从而确定是否继续构建。可能性是,如果程序在运行时出现任何问题,例如缺少文件,那么这是我自己解决的问题,然后平稳进行。

在我的情况下,使用非零退出代码的利弊有哪些?


1
就我个人而言,我经常使用退出代码。请记住,atexit处理程序是从调用退出的线程中调用的。如果您想要针对每个线程使用处理程序,则可以使用pthread_key_create解构器。 - Jens Munk
1
是的,正常终止包括退出代码为非零的情况。在一个小例子中自己尝试一下。 - Veltas
1
@Veltas 我相信你;我认为我总是混淆“正常程序终止”和“成功程序终止”的术语。如果你提供一个答案,我可以接受 - 谢谢。 - Engineer
2
我想指出,如果程序终止的情况下,清理所有内容是没有意义的(实际上是不鼓励的)。这样做只会迫使操作系统将可能已经移出系统内存的资源交换回来,而这些内存几秒钟后就会被丢弃。只需终止进程;这将隐式释放所有内存并关闭所有句柄。唯一需要进行清理的原因是在Valgrind或类似软件中运行以检查是否存在内存泄漏。在atexit处理程序中,您应该仅放置操作系统无法为您清理的内容。 - datenwolf
@Veltas:我指的是迭代每个类实例,删除/释放它,关闭每个打开的操作系统句柄等等。这太疯狂了。当然,在程序终止之前必须完成的事情是可以的(例如完成一些文件输出或重置调制解调器)。 - datenwolf
显示剩余12条评论
1个回答

9

触发已注册的atexit()函数

与您的问题相反,使用atexit()注册的函数仍会在程序尝试返回非零终止状态给主机环境时调用。调用exit()main()函数返回将触发使用atexit()注册的函数,无论给出的值是什么。

示例:

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

void print_stuff(void)
{
    puts("Stuff");
}

int main(void)
{
    atexit(print_stuff);
    exit(1);
}

这将打印Stuff,即使返回1

技术细节

根据ISO C标准,atexit()中注册的函数在调用exit()后被调用。以下情况也被定义为调用exit()(因此触发使用atexit()注册的函数):
  1. main()返回等同于调用exit()

  2. 最终线程调用thrd_exit()后,会调用exit(EXIT_SUCCESS)

以下是可能实现定义的调用exit()的来源:
  1. SIGTERM的默认信号处理程序。

  2. 使用set_constraint_handler_s()之前的默认约束处理程序。

标准提到了以下情况,exit()和使用atexit()注册的函数的调用被规避:
  1. 未处理的 SIGABRT 或由使用 signal() 注册的函数处理完的 SIGABRTSIGABRT 可以由 abort() 引发。

  2. 调用 _Exit()

  3. 调用 quick_exit()

您的实现的主机环境可能在某些情况下终止程序而不调用使用 atexit() 注册的函数,例如在 segfault 之后。

关于游戏的退出代码

对于游戏来说,您选择的退出代码并不太重要。是的,您不会依赖于一个 shell 脚本来运行游戏并向用户报告错误。错误反馈可能更有用,例如弹出对话框、日志或像 Linux 这样的系统的 stderr。


不错。你已经得到了我的点赞;我不能再给你了。我们可能应该清理所有这些评论。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接