3.1.2 Error handling
Exceptions change the normal flow of execution when abnormal events occur. Due to the lack of language-level support for exceptions in C, a different means of expressing that an operation could not successfully complete a request is needed. The standard library functions setjmp() and longjmp() are sometimes used to replicate the exception mechanism of other languages, but this approach is cumbersome to use and has been found to have poor performance (Jung et al. 2008).1 A simpler approach is to use the return value of operations for error information, and use an output argument for the true return value of the operation (if any). This technique does have the unfortunate side effect of requiring that return values are checked after each and every invocation, as seen in Listing 3.3.
Error information can be provided in a variety of ways:
Boolean return value. The simplest approach is arguably to require that operations return a boolean value indicating whether the invocation was successful. With this approach, it is not possible to easily determine the source of an error, to differentiate between different errors or to present human-readable information about the error.
Enumerator return value. Using an enumerated type makes it possible to differentiate between different errors, while retaining most of the simplicity of using a boolean return value.
Integer return value. An integer can obviously hold the same information as a boolean value or an enumerator. Additional information may be conveyed by dividing the integer space into different regions—for instance, the most significant bit can be used to signal success or failure, the next four bits can be used to communicate the severity of the error, and the remaining bits used to index into a table containing static information on various errors, including localized error messages.
Object return value. The most flexible way of conveying information on an error, of the four options presented here, is to return a reference to an object if an error occurred, or NULL otherwise. Such an object can contain a wealth of information on the error, possibly including a stack trace and localized error messages. The downside to this flexibility is that memory must likely be allocated dynamically, which the client must take care to deallocate.
Despite the shortcomings of using a boolean return value, this is the approach used here, due to its simplicity.
- Specifically, elaborate runtime book-keeping is needed to ensure that nested “catch” clauses work as intended, and clean-up, in the form of “finally” sections, is cumbersome to implement. In addition, this approach does not work well with component technology, as an exception may be thrown by an operation written in one language, and caught by an operation written in another, possibly interpreted, language.