exceptions4c 4.0
Exceptions for C
|
Bring the power of exceptions to your C applications!
This library consists of two files:
To use it in your project, include the header file in your source code files.
And then link your program against the library code.
Create meaningful exceptions that reflect problematic situations in the program.
An exception type is a simple structure with an optional supertype and a default error message.
Exception handling lets a program deal with errors without crashing. When something goes wrong, the program pauses its normal flow, jumps to code that handles the issue, and then either recovers or exits cleanly.
This library provides the following macros that are used to handle exceptions:
When we THROW an exception, the flow of the program moves to the appropriate CATCH block. If the exception is not handled by any of the blocks in the current function, it propagates up the call stack to the function that called the current function. This continues until the top level of the program is reached. If no block handles the exception, the program terminates and an error message is printed to the console.
Use THROW to trigger an exception when something goes wrong.
When we THROW an exception, the flow of the program moves from the TRY block to the appropriate CATCH block. If the exception is not handled by any of the blocks in the current function, it propagates up the call stack to the function that called the current function. This continues until the top level of the program is reached. If no block handles the exception, the program terminates and an error message is printed to the console.
printf
. Additionally, if you don't provide an error message, the default one for that exception type will be used.Use a TRY block to wrap code that might cause an exception.
These code blocks, by themselves, don't do anything special. But they allow the introduction of other blocks that do serve specific purposes.
goto
, break
, continue
, or return
.To prevent the program from crashing, exceptions need to be handled properly in designated sections of the code.
Use a CATCH block to handle a specific type of exceptions when they occur.
If the type in the CATCH block is the same as (or a supertype of) the thrown exception, then the block will be used to handle it.
One or more CATCH blocks can follow a TRY block. Each CATCH block must specify the type of exception it handles. If its type doesn't match the thrown exception, then that block is ignored, and the exception may be caught by the following blocks.
On the other hand, the CATCH_ALL block is a special block that can handle all types of exceptions.
Only one CATCH_ALL block is allowed per TRY block, and it must appear after all type-specific CATCH blocks if any are present.
A FINALLY block always runs, no matter whether an exception happens or not.
This block is optional. And, for each TRY block, there can be only one FINALLY block. If an exception occurs, the FINALLY block is executed after the CATCH or block that can handle it. Otherwise, it is executed after the TRY block.
This is a powerful design pattern for resource management. It is a clean and terse way to handle the acquisition and disposal of all kinds of resources.
These macros will help you make sure that no resource is leaked in your program.
A USING block allows you to easily acquire and dispose of a resource. It is similar to a for
statement, because it receives three comma-separated expressions that will be evaluated in order.
Both these expressions and the code block that uses the resource are free to throw exceptions.
pet = pet_find(id)
.pet != NULL
holds true, then the USING block will be executed.pet
will be disposed of, using the expression pet_free(pet)
, no matter whether an exception happens or not.You can append CATCH blocks to deal with exceptions that may happen during the manipulation of the resource. Just remember: by the time the CATCH block is executed, the resource will already have been disposed of.
Use a WITH block when the steps to acquire a resource are more complex than simply evaluating an expression. It works exactly the same as the USING block, except that you can write the code block in charge of actually acquiring the resource.
To customize the way this library behaves you may configure a structure that represents the exception context of the program.
Use e4c_get_context to retrieve the current exception context of the program.
Then use this object to set up different handlers.
Exceptions support custom data. By default, this data is left uninitialized when an exception is thrown.
You can set a custom exception initializer and your function will be executed whenever an exception is thrown.
You can also set a exception finalizer to execute your function whenever an exception is deleted.
By default, when an exception reaches the top level of the program, it gets printed to the standard error stream.
You can customize this behavior by setting the uncaught handler to a custom function that will be executed in the event of an uncaught exception.
stderr
you could save an error report in a local file.After the uncaught handler has been executed, the program is terminated by calling exit(EXIT_FAILURE)
.
You can make the library do anything else by setting the termination handler to execute a function in the event of program termination.
By default, a predefined exception context is provided and used by the library. But you can create a supplying function and pass it to e4c_set_context_supplier so you are in full control of your program's exception context.
There is an extension for this library, intended for multithreaded programs. exceptions4c-pthreads allows you to safely and concurrently use exceptions.
All you have to do is set the exception context supplier, so that each POSIX thread gets its own exception context.
In the event of an uncaught exception, instead of terminating the program, only the current thread will be canceled.
You can turn some standard signals such as SIGTERM
, SIGFPE
, and SIGSEGV
into exceptions so they can be handled in a regular CATCH block. For example, you could do that to prevent your program from crashing when a null pointer is dereferenced.
However, it's easy to enter undefined behavior territory, due to underspecified behavior and significant implementation variations regarding signal delivery while a signal handler is executed, so use this technique with caution.
signal
is used in a multithreaded program.This library relies on modern C features such as designated initializers, compound literals, and __VA_OPT__
.
Exception handling is based on standard C library functions setjmp
to save the current execution context and longjmp
to restore it. According to the documentation:
Upon return to the scope of
setjmp
:
- all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when
longjmp
was executed,- except for the non-volatile local variables in the function containing the invocation of
setjmp
, whose values are indeterminate if they have been changed since thesetjmp
invocation.
Since each TRY block invokes setjmp
, modified local variables in scope must be volatile
.
There are other exception handling implementations and libraries.
This library adheres to Semantic Versioning. All notable changes for each version are documented in a change log.
Head over to GitHub for the latest release.
The source code is available on GitHub.