Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C exit codes and atexit() in a realtime / non-batch app

I'm writing a game in which a number of different conditions can cause a failure, for example an image or shader failed to load, OpenGL failed to get a valid context, etc.

In an ideal world, I'd like to use exit codes in the way I believe was intended for C, i.e. non-zero for failure conditions. However, there are some factors that play into this:

  • In using non-zero exit codes, atexit() is not called, unfortunately for me as my program cleanup is handled hierarchically via a single atexit() registration in main(), which I like.
  • It occurs to me that given the nature of the application - a game has an indefinite running time unlike a batch program - there may never be a practical situation in which the exit codes are actually used. It's not like e.g. a command line image converter called by a Makefile which needs to know whether the program succeeded or failed via its exit code, thus determining whether or not to continue with the build. The likelihood is that if anything is wrong with the program at runtime, such as missing files, that's a problem I fix myself, then proceed smoothly.

What arguments can be made for and against using non-zero exit codes in my situation?

like image 707
Engineer Avatar asked Dec 02 '25 05:12

Engineer


1 Answers

Triggering registered atexit() functions

Contrary to your question, functions registered with atexit() are still called even when the program attempts to return a non-zero termination status to the host environment. A call to exit(), or the main() function returning, will trigger the functions registered with atexit(), regardless of the value given.

Example:

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

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

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

This will print Stuff, even though 1 is returned.

Technical details

By the ISO C standard, the functions registered in atexit() are called after exit() is called. The following situations are also defined as calling exit() (and therefore triggering the functions registered with atexit()):

  1. main() returning is equivalent to calling exit().

  2. After the final thread calls thrd_exit(), exit(EXIT_SUCCESS) is called.

The following are possible implementation-defined sources of calling exit():

  1. The default signal handler for SIGTERM.

  2. The default constraint handler before using set_constraint_handler_s().

The standard mentions the following situations where exit() and the calls to functions registered with atexit() are circumvented:

  1. An unhandled SIGABRT or a SIGABRT that finishes being handled by a function registered with signal(). SIGABRT can be raised by abort().

  2. Calling _Exit().

  3. Calling quick_exit().

The host environment for your implementation may terminate the program in some situations without calling functions registered with atexit(), such as after a segfault.

Regarding exit codes for a game

Your choice of exit code shouldn't matter too much for a game. Yes, you won't be relying on a shell script to run your game and report errors to the user. Error feedback is probably more useful in the form of dialog popups, a log, or stderr for systems like Linux.

like image 59
Veltas Avatar answered Dec 03 '25 19:12

Veltas