<<

GATS Companion to () Author: Garth Santor Editors: Trinh Hān Copyright Dates: 2020 Version: 1.0.0 (2020-03-19)

Overview The Standard Library exit() function is a favourite amongst C programmers, as it has the feel of a ‘get- out-of-jail-free card’ – it allows us to quit our application with little programming, and still look like professionals.

Background The first place most C programmers learned about the exit() function is from section 7.6 of The C (maybe more my generation than the current generation which probably found it on Stack Overflow…) This is a big clue as to its purpose: 7.6 Error Handling – Stderr and Exit. Early in your C-programming career, we learn that a good C program starts when main() is called and terminates when main() returns. Returning a zero if all ended well, or one (or non-zero) if it didn’t. int main() { // starts here! printf("Hello, world!\n"); return EXIT_SUCCESS; // ends here! }

The command shell receives the returned value and can conditionally it from a script. In the case of an error; report the error, and return your exit code. int main(int argc, char* argv[]) { if (argc < 1) { fprintf(stderr, "Error: insufficient arguments!\n"); return EXIT_FAILURE; } // carry on! … }

Simple enough… unless you are deep inside a series of nested function calls, loops, and conditionals. Each function would have to return an error code and every call would have to test for that error code. int foo() { … return EXIT_FAILURE; } double bar(int* eCodePtr) {

GATS Page 1 – 5 G. Santor

assert(eCodePtr != NULL); int eCode = foo(); if (eCode != EXIT_SUCCESS) { *eCodePtr = eCode; return 0.0; } // carry on! … } int caller() { int eCode; double x = bar(&eCode); if (eCode != EXIT_SUCCESS) return eCode; // carry on! … }

If we didn’t have to pass back the exit codes, our code could have been this… void foo() { … } double bar() { foo(); // carry on! … } void caller() { double x = bar(); // carry on! … }

Much simpler! So, you have to ask yourself, which version would you rather code? When a deadline is approaching? Which would you rather test? The first version only pleases developers that are paid by the line. programmers have always had an out – reset the stack pointer to the state when main() started, then jump to the termination routine. exit() is the C equivalent to that assembly programmer trick. It performs some cleanup, then jumps to the termination routine that is executed when main() returns. Now we can write:

GATS Page 2 – 5 G. Santor void foo() { … fprintf(stderr, "Error: foo failure!\n"); exit(EXIT_FAILURE); }

None of the callers need to be changed; or even recognize that an error is possible. The error handling is fully contained at the place the error is detected.

What is exit()? exit() is a function from the C Standard Library that causes normal program termination to occur. Normal program termination includes several cleanup activities:

• Calling functions passed to atexit(), in the reverse order of registration • Flushing and closing all C streams. • Removing files created by the function tmpfile() • Returning control to the host environment passing the parameter value to the host environment. Convention is zero or EXIT_SUCCESS indicates successful termination, and non-zero or EXIT_FAILURE indicates unsuccessful termination. In C++, exit() has some additional cleanup activities:

• The destructors of local objects of the current thread, and of static storage duration.

How do I use exit()? exit() and its companion function atexit() are found in the C Standard Library and in the C++ Standard Library . Example – FileCopy The full demo can be found in the FileCopy project of Exit Tutorial solution. int main(int argc, char* argv[]) { FILE* inStream = stdin; FILE* outStream = stdout;

if (argc > 3) { // No command-line arguments fprintf(stderr, "%s: has too many arguments (%d)\n", argv[0], argc); fprintf(stderr, "%s [sourcefile=stdin] [destinationfile=stdout]\n", argv[0]); exit(EXIT_FAILURE); } if (argc > 1) { // at least the input file entered on the command line if ((inStream = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "%s: can't open %s for input.\n", argv[0], argv[1]); exit(100); } } …

GATS Page 3 – 5 G. Santor

The use of exit() shows the intent that the program will terminate, and it guarantees that any file handles are closed. I use the value 100 to indicate that the failure has a particular meaning, specifically that the input file failed to open. How do I use atexit()? The atexit() function requires the address of a void/void function to be passed to it before program termination. That function will then be called during program shutdown. Example – ErrorLog First you’ll need to write a function that is callable by atexit(). void farewell1(void) { puts("Good"); } void farewell2(void) { puts("bye"); }

The function must have a void return, and a void parameter list. Register the function with: atexit(farewell1); atexit(farewell2);

Note that you don’t apply parenthesis to the function argument. When a function identifier is used without parenthesis, the compiler takes the address of the function (giving a function pointer). The farewell functions are then called in the reverse order when either exit() is called or function main() completes. The above example would print: bye Good

Because of the order they were registered. How to we pass arguments to an exit function? exit() functions don’t take parameters, so you can’t pass arguments to them. You can access global variables from within the exit() registered functions.

1. Create a global variable – initialized to a zero or NULL state (pointers and Booleans are commonly used). 2. Implement your exit() function to read the global variable (it represents the parameter). 3. Register your function with atexit(). 4. Set the global variable when you want to ‘pass an argument.’ 5. Call exit(). 6. Your atexit() registered function will be invoked and the global variable can be read.

GATS Page 4 – 5 G. Santor

See the ErrorLog demo for the code.

When do I use exit()? How do I know when to use exit()? You can use exit in the following situations: Any place your program terminates. • exit() performs file closures (like having a destructor that closes the file). • Documents the programmer’s intent. A call to exit() makes it very clear that the programmer intents to leave the program, and not just the function. The requires context to determine which of the two scenarios (leaving the function or leaving the program). On-termination cleanup • Closes open file handles. • Can emulate a C++ program’s destructor behaviour. Any on-termination cleanup can be registered with an atexit() function. I’ve sometimes used dynamic arrays or linked lists of objects (structures that need destructor-like behaviour on termination. Logging, persistence • Install a handler that logs the termination state. • Install a handle that saves the program state to a file. You would then load the program state from the file when the program is restarted. Common Practice The use of exit() after reporting a fatal error in conventional.

When not to use exit()? • Most obviously, when you don’t want to terminate your program. • When there is a cleaner object-oriented solution to the problem. Structured Exception handling is the most common alternative (not available in Standard C).

References C https://en.cppreference.com/w/c/program/exit C++ https://en.cppreference.com/w/cpp/utility/program/exit

GATS Page 5 – 5 G. Santor