exceptions4c header file More...
#include <stdlib.h>
#include <setjmp.h>
#include <stdbool.h>
Data Structures | |
struct | e4c_exception |
Represents an exception in the exception handling system. More... | |
struct | e4c_signal_mapping |
Represents a map between a signal and an exception. More... | |
Defines | |
Exception handling keywords | |
This set of keywords express the semantics of exception handling. | |
#define | try E4C_TRY |
Introduces a block of code aware of exceptions. | |
#define | catch(_exception_type_) E4C_CATCH(_exception_type_) |
Introduces a block of code capable of handling a specific kind of exceptions. | |
#define | finally E4C_FINALLY |
Introduces a block of code responsible for cleaning up the previous try block. | |
#define | retry(_max_retry_attempts_) E4C_RETRY(_max_retry_attempts_) |
Repeats the previous E4C_TRY (or E4C_USE) block entirely. | |
#define | throw(_exception_type_, _message_) E4C_THROW(_exception_type_, _message_) |
Signals an exceptional situation represented by an exception object. | |
#define | rethrow(_message_) E4C_RETHROW(_message_) |
Throws again the currently thrown exception, with a new message. | |
Dispose pattern keywords | |
This set of keywords express the semantics of automatic resource acquisition and disposal. | |
#define | with(_resource_, _dispose_) E4C_WITH(_resource_, _dispose_) |
Opens a block of code with automatic disposal of a resource. | |
#define | use E4C_USE |
Closes a block of code with automatic disposal of a resource. | |
#define | using(_type_, _resource_, _args_) E4C_USING(_type_, _resource_, _args_) |
Introduces a block of code with automatic acquisition and disposal of a resource. | |
#define | reacquire(_max_acquire_attempts_) E4C_REACQUIRE(_max_acquire_attempts_) |
Repeats the previous E4C_WITH block entirely. | |
Convenience macros for acquiring and disposing resources | |
These macros let you acquire and dispose different kinds of resources according to the dispose pattern. | |
#define | e4c_using_memory(_buffer_, _bytes_) |
Introduces a block of code with automatic acquisition and disposal of a memory buffer. | |
#define | e4c_using_file(_file_, _path_, _mode_) |
Introduces a block of code with automatic acquisition and disposal of a file stream. | |
#define | e4c_using_if(_type_, _resource_, _args_, _cond_, _exception_, _msg_) |
Introduces a block of code with automatic disposal of a resource and acquisition, under certain condition. | |
#define | e4c_using_if_not_null(_type_, _resource_, _args_, _exception_, _msg_) |
Introduces a block of code with automatic disposal of a resource and acquisition, preventing null pointers. | |
Integration macros | |
These macros are designed to ease the integration of external libraries which make use of the exception handling system. | |
#define | E4C_VERSION_NUMBER |
Provides the library version number. | |
#define | E4C_VERSION_THREADSAFE |
Provides the library thread mode (either single-thread or multi-thread) | |
#define | E4C_VERSION_MAJOR |
Provides the library major version number. | |
#define | E4C_VERSION_MINOR |
Provides the library minor version number. | |
#define | E4C_VERSION_REVISION |
Provides the library revision number. | |
#define | E4C_VERSION_STRING |
Provides the library version number as a string literal. | |
#define | E4C_EXCEPTION_MESSAGE_SIZE 128 |
Provides the maximum length (in bytes) of an exception message. | |
#define | e4c_reusing_context(_thrown_exception_) _E4C_REUSING_CONTEXT(_thrown_exception_) |
Reuses an existing exception context, otherwise, begins a new one and then ends it. | |
Other convenience macros | |
These macros provide a handy way to: begin (and end) implicitly a new exception context, express assertions, define and declare exceptions, and define arrays of signal mappings. | |
#define | e4c_using_context(_handle_signals_, _uncaught_handler_) _E4C_USING_CONTEXT(_handle_signals_, _uncaught_handler_) |
Introduces a block of code which will use a new exception context. | |
#define | e4c_assert(_condition_) |
Expresses a program assertion. | |
#define | throwf(_exception_type_, _format_,...) |
Throws an exception with a formatted message. | |
#define | rethrowf(_format_,...) |
Throws again the currently thrown exception, with a new, formatted message. | |
#define | E4C_DECLARE_EXCEPTION(_name_) |
Declares an exception. | |
#define | E4C_DEFINE_EXCEPTION(_name_, _message_, _super_) |
Defines an exception. | |
#define | E4C_SIGNAL_MAPPING(_signal_number_, _exception_) |
Represents a signal mapping literal. | |
#define | E4C_NULL_SIGNAL_MAPPING |
Represents a null signal mapping literal. | |
Typedefs | |
typedef void(* | e4c_uncaught_handler )(const e4c_exception *exception) |
This is the signature of a function which will be executed in the event of an uncaught exception. | |
Enumerations | |
enum | e4c_status { e4c_succeeded, e4c_recovered, e4c_failed } |
Represents the completeness of a code block aware of exceptions. More... | |
Functions | |
Exception context handling Functions | |
These functions enclose the scope of the exception handling system and retrieve the current exception context. | |
bool | e4c_context_is_ready (void) |
Checks if the current exception context is ready. | |
void | e4c_context_begin (bool handle_signals, e4c_uncaught_handler uncaught_handler) |
Begins an exception context. | |
void | e4c_context_end (void) |
Ends the current exception context. | |
void | e4c_context_set_signal_mappings (const e4c_signal_mapping *mappings) |
Assigns the specified signal mappings to the exception context. | |
const e4c_signal_mapping * | e4c_context_get_signal_mappings (void) |
Retrieves the signal mappings for the current exception context. | |
e4c_status | e4c_get_status (void) |
Returns the completeness status of the executing code block. | |
const e4c_exception * | e4c_get_exception (void) |
Returns the exception that was thrown. | |
Other integration and convenience functions | |
long | e4c_library_version (void) |
Gets the library version number. | |
bool | e4c_is_instance_of (const e4c_exception *instance, const e4c_exception *type) |
Returns whether an exception is of a given exception type. | |
void | e4c_print_exception (const e4c_exception *exception) |
Prints a fatal error message regarding the specified exception. | |
Variables | |
Predefined signal mappings | |
There is a predefined set of signal mappings. Signal mappings are used to convert signals into exceptions. Common signals are mapped to its corresponding exception, for example:
| |
const e4c_signal_mapping * | e4c_default_signal_mappings |
The array of predefined signal mappings. | |
Predefined exceptions | |
Built-in exceptions represent usual error conditions that might occur during the course of any program. They are organized into a pseudo-hierarchy, being | |
const e4c_exception | RuntimeException |
This is the root of the exception pseudo-hierarchy. | |
const e4c_exception | NotEnoughMemoryException |
This exception is thrown when the system runs out of memory. | |
const e4c_exception | NullPointerException |
This exception is thrown when an unexpected null pointer is found. | |
const e4c_exception | AssertionException |
This exception is thrown when an assertion does not hold. | |
const e4c_exception | FileOpenException |
This exception is thrown when a file cannot be opened. | |
const e4c_exception | SignalException |
This exception is the common supertype of all signal exceptions. | |
const e4c_exception | SignalAlarmException |
This exception is thrown when a time limit has elapsed. | |
const e4c_exception | SignalChildException |
This exception is thrown when a child process terminates. | |
const e4c_exception | SignalTrapException |
This exception is thrown when a condition arises that a debugger has requested to be informed of. | |
const e4c_exception | ErrorSignalException |
This exception is the common supertype of all error signal exceptions. | |
const e4c_exception | IllegalInstructionException |
This exception is thrown when the process attempts to execute an illegal instruction. | |
const e4c_exception | ArithmeticException |
This exception is thrown when the process performs an erroneous arithmetic operation. | |
const e4c_exception | BadPointerException |
This exception is thrown when the process tries to dereference an invalid pointer. | |
const e4c_exception | BrokenPipeException |
This exception is thrown when the process attempts to write to a broken pipe. | |
const e4c_exception | ControlSignalException |
This exception is the common supertype of all control signal exceptions. | |
const e4c_exception | StopException |
This exception is thrown to stop the process for later resumption. | |
const e4c_exception | KillException |
This exception is thrown to terminate the process immediately. | |
const e4c_exception | HangUpException |
This exception is thrown when the process' terminal is closed. | |
const e4c_exception | TerminationException |
This exception is thrown to request the termination of the process. | |
const e4c_exception | AbortException |
This exception is thrown to abort the process. | |
const e4c_exception | CPUTimeException |
This exception is thrown when the process has used up the CPU for too long. | |
const e4c_exception | UserControlSignalException |
This exception is the common supertype of all control signal exceptions caused by the user. | |
const e4c_exception | UserQuitException |
This exception is thrown when the user requests to quit the process. | |
const e4c_exception | UserInterruptionException |
This exception is thrown when the user requests to interrupt the process. | |
const e4c_exception | UserBreakException |
This exception is thrown when a user wishes to break the process. | |
const e4c_exception | ProgramSignalException |
This exception is the common supertype of all user-defined signal exceptions. | |
const e4c_exception | ProgramSignal1Exception |
This exception is thrown when user-defined conditions occur. | |
const e4c_exception | ProgramSignal2Exception |
This exception is thrown when user-defined conditions occur. |
exceptions4c header file
This header file needs to be included from in order to be able to use any of the keywords of the the exception handling system:
try
catch
finally
throw
with
using
In order to stop defining this keywords, there exists a E4C_NOKEYWORDS
compile-time parameter. When the keywords are not defined, the next set of alternative macros can be used to achieve the same functionality:
E4C_TRY
E4C_CATCH
E4C_FINALLY
E4C_THROW
E4C_WITH
E4C_USING
This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this software. If not, see <http://www.gnu.org/licenses/>.
#define catch | ( | _exception_type_ ) | E4C_CATCH(_exception_type_) |
Introduces a block of code capable of handling a specific kind of exceptions.
_exception_type_ | The type of exceptions to be handled |
catch
blocks are optional code blocks that must be preceded by try
, with/use
or using
blocks. Several catch
blocks can be placed next to one another.
When an exception is thrown from a try
block, the system looks for a catch
block to handle it. The first capable block (in order of appearance) will be executed and the exception is said to be caught.
The caught exception can be accessed through the function e4c_get_exception
.
If a catch
block handles (at compile-time) a generic type of exceptions, the specific type of the actual exception can be determined (at run-time) by comparing the type
of the caught exception against the type of another exceptions previously defined in the program.
try{ ... }catch(RuntimeException){ const e4c_exception * exception = e4c_get_exception(); if(exception->type == SignalException.type){ ... }else if(exception->type == NotEnoughMemoryException.type){ ... } }
However, this check compares the exception against a specific type. So, if the thrown exception was a subtype of the given exception type, this comparison would then yield false
. For example, in the previous example, if the thrown exception was of type BadPointerException
: it would be caught by the catch
block, because an instance of a BadPointerException
is also an instance of a RuntimeException
, but the comparison (exception->type == SignalException.type)
would yield false
because the type of the thrown exception was not strictly speaking SignalException
, but BadPointerException
.
There is a more powerful way to find out if the thrown exception is an instance of a given type of exception or any subtype. The function e4c_is_instance_of
can determines that:
try{ ... }catch(RuntimeException){ const e4c_exception * exception = e4c_get_exception(); if( e4c_is_instance_of(exception, SignalException.type) ){ ... }else if(exception->type == NotEnoughMemoryException.type){ ... } }
In this example, the if
condition would evaluate to true
because a BadPointerException
is an instance of a RuntimeException
.
After the catch
block completes, the finally
block (if any) is executed. Then the program continues with the next line following the set of try/catch/finally
blocks.
However, if an exception is thrown in a catch
block, then the finally
will be executed right away and the system will look for an outter catch
block to handle it.
Only one of all the catch
blocks will be executed for each try
block, even though the executed catch
block throws another exception.
#define e4c_assert | ( | _condition_ ) |
\ _E4C_ASSERT(_condition_)
Expresses a program assertion.
An assertion is a mechanism to express that the developer thinks that a specific condition is always met at some point of the program.
e4c_assert
is a convenient way to insert debugging assertions into a program. The NDEBUG
compile-time parameter determines whether the assumptions will be actually verified by the program at run-time.
In absence of NDEBUG
, the assertion statements will be ignored and therefore will have no effect on the program, not even evaluating the condition. Therefore expressions passed to e4c_assert
must not contain side-effects, since they will not happen when debugging is disabled.
In presence of NDEBUG
, the assertion statements will verify that the condition is met every time the program reaches that point of the program.
If the assertion does not hold at any time, then an AssertionException
will be thrown to indicate the programming error. This exception cannot be caught whatsoever. The program (or current thread) will be terminated.
The main advantage of using this assertion mechanism (as opposed to the macros provided by the standard header file assert.h
) is that before actually exiting the program or thread, all of the pending finally
blocks will be executed.
_condition_ | A predicate that must evaluate to true |
#define E4C_DECLARE_EXCEPTION | ( | _name_ ) |
\ _E4C_DECLARE_EXCEPTION(_name_)
Declares an exception.
This macro introduces the name of an extern
, const
exception which will be available to be thrown or caught. It is only a declaration (i.e. the exception has to be defined somewhere else). This macro is intended to be used inside a header file.
_name_ | Name of the new exception |
#define E4C_DEFINE_EXCEPTION | ( | _name_, | |
_message_, | |||
_super_ | |||
) |
\ _E4C_DEFINE_EXCEPTION(_name_, _message_, _super_)
Defines an exception.
This macro allocates a new, const
exception.
_name_ | Name of the new exception |
_message_ | Default message of the new exception |
_super_ | Supertype of the new exception |
#define E4C_EXCEPTION_MESSAGE_SIZE 128 |
Provides the maximum length (in bytes) of an exception message.
#define E4C_NULL_SIGNAL_MAPPING |
\ _E4C_NULL_SIGNAL_MAPPING
Represents a null signal mapping literal.
This macro comes in handy for terminating arrays of e4c_signal_mapping
s.
#define e4c_reusing_context | ( | _thrown_exception_ ) | _E4C_REUSING_CONTEXT(_thrown_exception_) |
Reuses an existing exception context, otherwise, begins a new one and then ends it.
This macro lets library developers use the exception framework, regardless of whether the library clients are unaware of the exception handling system. In a nutshell, function libraries can use try
, catch
, throw
, etc. whether the client previously began an exception context or not.
You must not use this macro unless you are implementing some functionality which is to be called from another program, potentially unaware of exceptions.
A block introduced by e4c_reusing_context
is guaranteed to take place inside an execution context. When the block completes, the system returns to its previous status (if it was necessary to open a new exception context, it will be automatically closed).
This way, when an external functions encounters an error, it may either throw an exception (when the caller is aware of the exception system), or otherwise return an error code (when the caller did not open an exception context).
e4c_reusing_context
needs to be given a variable that will point to whichever exception thrown inside the block. This variable must be a pointer to e4c_exception
and will be set to NULL
if no exception was thrown inside the block.
int library_public_function(void * pointer, int number){ /* We don't know where this function is going to be called from, so: * We cannot use "try", "throw", etc. right here, because the exception context COULD be uninitialized at this very moment. * We cannot call "e4c_context_begin" either, because the exception context COULD be already initialized. If we have to make use of the exception handling system, we need to "reuse" the existing exception context or "use" a new one. */ e4c_exception * exception; e4c_reusing_context(exception){ /* Now we can safely use "try", "throw", etc. */ if(pointer == NULL){ throw(NullPointerException); } library_private_function(pointer, number); } if(exception != NULL){ /* We got here because: * There was no exception context * An exception was caught during the execution of the block * The block was closed Now we should return an error code to the caller, which is unaware of the exception system (if the caller was aware, the exception would have been simply propagated). */ if(exception->type == NullPointerException){ return(-3); }else if(exception->type == NotEnoughMemoryException){ return(-2); } return(-1); } /* If we got here, it means that our block completed successfully. We need to return a "success" status code. */ return(0); }
Next, the semantics of e4c_reusing_context
are explained, step by step.
_thrown_exception_
will be NULL
. _thrown_exception_
will point to it. _thrown_exception_
is NULL
then the block completed successfully. The function may then return a "success" status code. If you need to perform any cleanup, you should place it inside a finally
block, for example:
... e4c_reusing_context(exception){ void * buffer = NULL; try{ buffer = malloc(1024); ... }finally{ free(buffer); } } ...
If you need to rely on the signal handling system, you may call e4c_context_set_signal_mappings
explicitely. You must take into account that you could be hijacking your client's original signal mappings, so you should also call e4c_context_get_signal_mappings
in order to restore the previous signal mappings when you are done.
const e4c_signal_mapping new_mappings[] = { E4C_SIGNAL_MAPPING(SIGABRT, Exception1), E4C_SIGNAL_MAPPING(SIGINT, Exception2), ... E4C_NULL_SIGNAL_MAPPING }; ... e4c_reusing_context(exception){ const e4c_signal_mapping * old_mappings = e4c_context_get_signal_mappings(); e4c_context_set_signal_mappings (new_mappings); try{ ... }finally{ e4c_context_set_signal_mappings (old_mappings); } } ...
This macro only begins a new exception context if there is no one, already begun, to be used whereas e4c_using_context
always attempts to begin a new one.
_thrown_exception_ | A reference to the exception that was thrown inside the block (if any), NULL otherwise. |
#define E4C_SIGNAL_MAPPING | ( | _signal_number_, | |
_exception_ | |||
) |
\ _E4C_SIGNAL_MAPPING(_signal_number_, _exception_)
Represents a signal mapping literal.
This macro comes in handy for initializing arrays of e4c_signal_mapping
s.
_signal_number_ | Numeric value of the signal to be converted |
_exception_ | Exception representing the signal |
#define e4c_using_context | ( | _handle_signals_, | |
_uncaught_handler_ | |||
) | _E4C_USING_CONTEXT(_handle_signals_, _uncaught_handler_) |
Introduces a block of code which will use a new exception context.
This macro begins a new exception context to be used by the code block right next to it. When the code completes, e4c_context_end
will be called implicitly.
This macro is very convenient when the beginning and the ending of the current exception context are next to each other. For example, there is no semantic difference between this two blocks of code:
/* block 1 */ e4c_context_begin (e4c_true, NULL); /* ... */ e4c_context_end(); /* block 2 */ e4c_using_context(e4c_true, NULL){ /* ... */ }
This macro always attempts to begin a new exception context, whereas e4c_reusing_context
only does if there is no exception context, already begun, to be used.
This macro should be used whenever possible, rather than doing the explicit, manual calls to e4c_context_begin
and e4c_context_end
, because it is less prone to errors.
_handle_signals_ | If true , the signal handling system will be set up with the default mapping. |
_uncaught_handler_ | If not NULL , this function will be called in the event of an uncaught exception. |
#define e4c_using_file | ( | _file_, | |
_path_, | |||
_mode_ | |||
) |
e4c_using_if_not_null( file, _file_, (_path_, _mode_), \ FileOpenException, "Could not open file: " #_path_)
Introduces a block of code with automatic acquisition and disposal of a file stream.
_file_ | The file to be acquired, used and then disposed |
_path_ | The path of the file to be opened |
_mode_ | The access mode for the file |
This macro lets you acquire and dispose (open and close) files according to the dispose pattern:
FILE * file; e4c_using_file(file, "log.txt", "a"){ /* implicit: file = fopen("log.txt", "a"); */ fputs("hello, world!\n", file); /* implicit: fclose(file); */ }
If fopen
returns NULL
then the exception FileOpenException
will be automatically thrown.
The specific cause of the error can be determined by checking the error_number
of the thrown exception (it captures the value of errno
).
#define e4c_using_if | ( | _type_, | |
_resource_, | |||
_args_, | |||
_cond_, | |||
_exception_, | |||
_msg_ | |||
) |
E4C_WITH(_resource_, e4c_dispose_##_type_){ \
_resource_ = e4c_acquire_##_type_ _args_; \
if( !(_cond_) ) E4C_THROW(_exception_, _msg_); \
}E4C_USE
Introduces a block of code with automatic disposal of a resource and acquisition, under certain condition.
_type_ | The type of the resource |
_resource_ | The resource to be acquired, used and then disposed |
_args_ | A list of arguments to be passed to the acquisition function |
_cond_ | The condition which has to be satisfied in order to consider the acquisition complete |
_exception_ | The exception to be thrown if the acquisition function does not satisfy the specified condition |
_msg_ | The exception message |
This macro will attempt the acquisition of the resource and then will check the given condition. If the condition evaluates to false, then the specified exception will be thrown, and therefore, the disposal of the resource will not take place.
This is a convenience macro for reusing legacy C standard functions which don't throw exceptions when the acquisition fails. For example:
# define e4c_acquire_memory malloc # define e4c_dispose_memory(_memory_, _failed_) free(_memory_) # define e4c_using_memory(_resource_, _bytes_) \ e4c_using_if(memory, _resource_, ( _bytes_ ), _resource_ != NULL, \ NotEnoughMemoryException )
#define e4c_using_if_not_null | ( | _type_, | |
_resource_, | |||
_args_, | |||
_exception_, | |||
_msg_ | |||
) |
e4c_using_if(_type_, _resource_, _args_, _resource_ != NULL, \ _exception_, _msg_)
Introduces a block of code with automatic disposal of a resource and acquisition, preventing null pointers.
_type_ | The type of the resource |
_resource_ | The resource to be acquired, used and then disposed |
_args_ | A list of arguments to be passed to the acquisition function |
_exception_ | The exception to be thrown if the acquisition function yields a NULL pointer |
_msg_ | The exception message |
#define e4c_using_memory | ( | _buffer_, | |
_bytes_ | |||
) |
e4c_using_if_not_null(memory, _buffer_, (_bytes_), \ NotEnoughMemoryException, "Could not allocate memory for '" #_buffer_ "'.")
Introduces a block of code with automatic acquisition and disposal of a memory buffer.
_buffer_ | The buffer to be acquired, used and then disposed |
_bytes_ | The amount of memory to be allocated (in bytes) |
This macro lets you acquire and dispose memory according to the dispose pattern:
void * buffer; e4c_using_memory(buffer, 1024){ /* implicit: buffer = malloc(1024); */ memset(buffer, 0, 1024); /* implicit: free(buffer); */ }
If malloc
returns NULL
then the exception NotEnoughMemoryException
will be automatically thrown.
#define E4C_VERSION_MAJOR |
\ _E4C_VERSION(_E4C_V_MAJOR)
Provides the library major version number.
The library major version number is an int
value which is incremented from one release to another when there are significant changes in functionality.
#define E4C_VERSION_MINOR |
\ _E4C_VERSION(_E4C_V_MINOR)
Provides the library minor version number.
The library minor version number is an int
value which is incremented from one release to another when only minor features or significant fixes have been added.
#define E4C_VERSION_NUMBER |
\ _E4C_VERSION(_E4C_V_NUMBER)
Provides the library version number.
The library version number is a long
value which expresses:
The multi-thread (or thread-safe) mode can be obtained by compiling the library with the E4C_THREADSAFE
compile-time parameter.
The formula to encode these version numbers into a single long
value is:
THREADSAFE * 10000000 + MAJOR * 1000000 + MINOR * 1000 + REVISION
These numbers can be obtained separately through the next macros:
The library version number can be also obtained as a string literal in the format "MAJOR.MINOR.REVISION (THREADSAFE)" through the E4C_VERSION_STRING
macro.
This version number can be considered as the compile-time library version number, as opposed to the run-time library version number (associated with the actual, compiled library). This run-time version number can be obtained through the e4c_library_version
function.
The library must be compiled with the corresponding header (i.e. library version number should be equal to header version number).
#define E4C_VERSION_REVISION |
\ _E4C_VERSION(_E4C_V_REVISION)
Provides the library revision number.
The library revision number is an int
value which is incremented from one release to another when minor bugs are fixed.
#define E4C_VERSION_STRING |
\ _E4C_VERSION(_E4C_V_STRING)
Provides the library version number as a string literal.
The format of the string literal is: "MAJOR.MINOR.REVISION (THREADSAFE)".
#define E4C_VERSION_THREADSAFE |
\ _E4C_V_THREADSAFE
Provides the library thread mode (either single-thread or multi-thread)
When the library is compiled with the E4C_THREADSAFE
compile-time parameter, E4C_VERSION_THREADSAFE
will yield the int
value 1
(meaning multi-thread mode), otherwise it will yield the int
value 0
(meaning single-thread mode).
#define finally E4C_FINALLY |
Introduces a block of code responsible for cleaning up the previous try
block.
finally
blocks are optional code blocks that must be preceded by try
, with/use
or using
blocks. It is allowed to place, at most, one finally
block.
The finally
block can determine the completeness of the try/catch
set of blocks through the function e4c_get_status
. The thrown exception (if any) can also be accessed through the function e4c_get_exception
.
try{ ... }finally{ switch( e4c_get_status() ){ case e4c_succeeded: ... break; case e4c_recovered: ... break; case e4c_failed: ... break; } }
Only one of all the catch
blocks will be executed for each try
block, even though the executed catch
block throws another exception.
#define reacquire | ( | _max_acquire_attempts_ ) | E4C_REACQUIRE(_max_acquire_attempts_) |
Repeats the previous E4C_WITH block entirely.
This macro discards any thrown exception (if any) and repeats the previous with
block, up to a specified maximum number of attempts. If the acquisition completes, then the use
block will be executed.
This macro is intended to be used in catch
or finally
blocks, next to a with... use
or using
block when the resource acquisition failed, as a quick way to fix an error condition and try to acquire the resource again.
image_type * image; const char * image_path = image_get_user_avatar(); with(image, e4c_image_dispose){ image = e4c_image_acquire(image_path); }use{ image_show(image); }catch(ImageNotFoundException){ image_path = image_get_default_avatar(); reacquire(1); }
If the specified maximum number of attempts is zero, then the with
block can eventually be attempted an unlimited number of times. Care must be taken in order not to create an infinite loop.
This macro won't return control unless the with
block has already been attempted, at least, the specified maximum number of times.
Once the resource has been acquired, the use
block can also be repeated alone through the retry
macro.
image_type * image; const char * image_path = image_get_user_avatar(); display_type * display = display_get_user_screen(); with(image, e4c_image_dispose){ image = e4c_image_acquire(image_path); }use{ image_show(image, display); }catch(ImageNotFoundException){ image_path = image_get_default_avatar(); reacquire(1); }catch(DisplayException){ display = display_get_default_screen(); retry(1); }
_max_acquire_attempts_ | The maximum number of attempts to reacquire |
#define rethrow | ( | _message_ ) | E4C_RETHROW(_message_) |
Throws again the currently thrown exception, with a new message.
This macro creates a new instance of the thrown exception, with a more specific message.
rethrow
is intended to be used in a catch
block and the purpose is to refine the message of the currently caught exception. The previous exception (and its message) will be stored as the cause of the newly thrown exception.
try{ image = read_file(file_path); }catch(FileOpenException){ rethrow("Image not found."); }
The semantics of this macro are the same as for the throw
macro.
_message_ | The new message describing the exception. It should be more specific than the current one |
#define rethrowf | ( | _format_, | |
... | |||
) |
\ E4C_RETHROWF( (_format_), __VA_ARGS__ )
Throws again the currently thrown exception, with a new, formatted message.
This macro creates a new instance of the thrown exception, with a more specific message.
This is a handy way to create a new instance of the thrown exception, with a more specific, formatted message.
try{ image = read_file(file_path); }catch(FileOpenException){ rethrowf("Image '%s' not found.", title); }
This macro relies on two features that were introduced in the ISO/IEC 9899:1999 (also known as C99) revision of the C programming language standard in 1999:
vsnprintf
In order not to create compatibility issues, this macro will only be defined when the __STDC_VERSION__
compile-time parameter is defined with a value greater than or equal to 199901L
, or HAVE_C99_VARIADIC_MACROS
is defined.
The semantics of this macro are the same as for the throw
macro.
At least one argument must be passed right after the format string. The message will be composed through the function vsnprintf
with the specified format and variadic arguments. For further information on the formatting rules, you may look up the specifications for the function vsnprintf
.
_format_ | The string containing the specifications that determine the output format for the variadic arguments. |
... | The variadic arguments that will be formatted according to the format control. |
#define retry | ( | _max_retry_attempts_ ) | E4C_RETRY(_max_retry_attempts_) |
Repeats the previous E4C_TRY (or E4C_USE) block entirely.
This macro discards any thrown exception (if any) and repeats the previous try
or use
block, up to a specified maximum number of attempts.
This macro is intended to be used in catch
or finally
blocks as a quick way to fix an error condition and try again.
const char * file_path = config_get_user_defined_file_path(); try{ config = read_config(file_path); }catch(ConfigException){ file_path = config_get_default_file_path(); retry(1); rethrow("Wrong defaults."); }
If the specified maximum number of attempts is zero, then the block can eventually be attempted an unlimited number of times. Care must be taken in order not to create an infinite loop.
This macro won't return control unless the block has already been attempted, at least, the specified maximum number of times.
At a catch
block, the current exception is considered caught, whether the retry
takes place or not. If you want the exception to be propagated when the maximum number of attempts has been reached, then you must (re)throw it again.
int dividend = 100; int divisor = 0; int result = 0; try{ result = dividend / divisor; do_something(result); }catch(RuntimeException){ divisor = 1; retry(1); rethrow("Error (not a division by zero)."); }
At a finally
block, the current exception (if any) will be propagated if the retry
does not take place, so you don't need to (re)throw it again.
int dividend = 100; int divisor = 0; int result = 0; try{ result = dividend / divisor; do_something(result); }finally{ if( e4c_get_status() == e4c_failed ){ divisor = 1; retry(1); /* when we get here, the exception will be propagated */ } }
_max_retry_attempts_ | The maximum number of attempts to retry |
#define throw | ( | _exception_type_, | |
_message_ | |||
) | E4C_THROW(_exception_type_, _message_) |
Signals an exceptional situation represented by an exception object.
Creates a new instance of the specified type of exception and then throws it. The provided message is copied into the thrown exception, so it can be freely deallocated. If NULL
is passed, then the default message for that type of exception will be used.
When an exception is thrown, the exception handling framework looks for the appropriate catch
block that can handle the exception. The system unwinds the call chain of the program and executes the finally
blocks it finds.
When no catch
block is able to handle an exception, the system eventually gets to the main function of the program. This situation is called an uncaught exception.
_exception_type_ | The type of exception to be thrown |
_message_ | The ad hoc message describing the exception. If NULL , then the default message for the specified exception will be used |
#define throwf | ( | _exception_type_, | |
_format_, | |||
... | |||
) |
\ E4C_THROWF( (_exception_type_), (_format_), __VA_ARGS__ )
Throws an exception with a formatted message.
This is a handy way to compose a formatted message and throw an exception on the fly:
int bytes = 1024; void * buffer = malloc(bytes); if(buffer == NULL){ throwf(NotEnoughMemoryException, "Could not allocate %d bytes.", bytes); }
This macro relies on two features that were introduced in the ISO/IEC 9899:1999 (also known as C99) revision of the C programming language standard in 1999:
vsnprintf
In order not to create compatibility issues, this macro will only be defined when the __STDC_VERSION__
compile-time parameter is defined with a value greater than or equal to 199901L
, or HAVE_C99_VARIADIC_MACROS
is defined.
The semantics of this macro are the same as for the throw
macro.
At least one argument must be passed right after the format string. The message will be composed through the function vsnprintf
with the specified format and variadic arguments. For further information on the formatting rules, you may look up the specifications for the function vsnprintf
.
_exception_type_ | The type of exception to be thrown |
_format_ | The string containing the specifications that determine the output format for the variadic arguments. |
... | The variadic arguments that will be formatted according to the format control. |
#define use E4C_USE |
Closes a block of code with automatic disposal of a resource.
A use
block must always be preceded by a with
block. These two macros are designed so the compiler will complain about dangling with
or use
blocks.
A code block introduced by the use
keyword will only be executed if the acquisition of the resource completes without any exception.
Either if the use
block completes or not, the disposal function will be executed right away.
#define using | ( | _type_, | |
_resource_, | |||
_args_ | |||
) | E4C_USING(_type_, _resource_, _args_) |
Introduces a block of code with automatic acquisition and disposal of a resource.
_type_ | The type of the resource |
_resource_ | The resource to be acquired, used and then disposed |
_args_ | A list of arguments to be passed to the acquisition function |
The specified resource will be acquired, used and then disposed. The automatic acquisition and disposal is achieved by calling the magic functions:
_type_ e4c_acquire__type_(_args_)
void e4c_dispose_type_(_resource_, _failed_)
The resource will be acquired implicitly by assigning to it the result of the magic acquisition function e4c_acquire__type_
.
The semantics of the automatic acquisition and disposal are the same as for blocks introduced by the keyword with
. For example, a using
block can also precede catch
and finally
blocks.
#define with | ( | _resource_, | |
_dispose_ | |||
) | E4C_WITH(_resource_, _dispose_) |
Opens a block of code with automatic disposal of a resource.
_resource_ | The resource to be disposed |
_dispose_ | The name of the disposal function (or macro) |
The with
keyword is used to encapsulate the Dispose Pattern. It must be followed by the use
keyword.
In addition, the use
block can precede catch
and finally
blocks.
This pattern consists of two separate blocks and an implicit call to a given function:
with
block is responsible for the resource acquisition use
block makes use of the resource The with
keyword guarantees that the disposal function will be called if and only if the acquisition block completed without an error (i.e. no exteption being thrown from the acquisition block).
If the with
block does not complete, then neither the disposal function nor the use
block will be ever executed.
The disposal function is called right after the use
block. If an exception was thrown, the catch
or finally
blocks (if any) will take place after the disposal of the resource.
When called, the disposal function will receive two arguments:
use
block did not complete This way, different actions can be taken depending on the success or failure of the block. For example, commiting or rolling back a transaction resource.
Legacy functions can be reused by defining macros. For example, a file resource needs to be closed regardless of the errors. Since the function fclose
only takes one parameter, we could define the next macro:
# define e4c_dispose_file(_file_, _failed_) fclose(_file_)
The typical usage of a with
block will be:
with(foo, e4c_dispose_foo){ foo = e4c_acquire_foo(foobar); someAssertion(foo, bar); ... }use{ doSomething(foo); ... }catch(FooAcquisitionFailed){ recover1(); ... }catch(somethingElseFailed){ recover2(); ... }finally{ cleanup(); ... }
Nonetheless, one-liners fit nicely too:
with(foo, e4c_dispose_foo) foo = e4c_acquire_foo(bar, foobar); use doSomething(foo);
There is a way to lighten up even more this pattern by defining convenience macros, customized for a specific kind of resources. For example, e4c_using_file
or e4c_using_memory
.
typedef void(* e4c_uncaught_handler)(const e4c_exception *exception) |
This is the signature of a function which will be executed in the event of an uncaught exception.
These functions are not allowed to try and recover the current exception context. Moreover, the program (or current thread) will terminate right after the function returns.
exception | The uncaught exception |
enum e4c_status |
Represents the completeness of a code block aware of exceptions.
The symbolic values representing the status of a block help to distinguish between different possible situations inside a finally
block. For example, different cleanup actions can be taken, depending on the status of the block.
try{ ... }finally{ switch( e4c_get_status() ){ case e4c_succeeded: ... break; case e4c_recovered: ... break; case e4c_failed: ... break; } }
void e4c_context_begin | ( | bool | handle_signals, |
e4c_uncaught_handler | uncaught_handler | ||
) |
Begins an exception context.
This function begins the current exception context to be used by the program (or current thread), until e4c_context_end
is called.
A program (or thread) must not use the keywords try
, catch
, throw
, etc. prior to calling e4c_context_begin
. Such programming error will lead to an abrupt exit of the program or thread.
Calling e4c_context_begin
twice is also considered a programming error, and therefore the program (or thread) will exit abruptly too. Nevertheless, e4c_context_begin
can be called several times if, and only if, e4c_context_end
is called in between.
The signal handling system can be automatically initialized with the default signal mapping via handle_signals
parameter when calling e4c_context_begin
. This is equivalent to:
e4c_context_set_signal_mappings(e4c_default_signal_mappings);
Note that the behavior of signal
is undefined in a multithreaded program, so use the signal handling system with caution.
In addition, a handling function can be specified to be called in the event of an uncaught exception. This function will be called before exiting the program or thread.
There exist a convenience function to be used as the default uncaught handler, called e4c_print_exception
. This function simply prints information regarding the exception to stderr
, and then exits.
handle_signals | If true , the signal handling system will be set up with the default mapping. |
uncaught_handler | If not NULL , this function will be called in the event of an uncaught exception. |
void e4c_context_end | ( | void | ) |
Ends the current exception context.
This function ends the current exception context.
A program (or thread) must begin an exception context prior to calling e4c_context_end
. Such programming error will lead to an abrupt exit of the program (or thread).
const e4c_signal_mapping* e4c_context_get_signal_mappings | ( | void | ) |
Retrieves the signal mappings for the current exception context.
This function retrieves the current array of mappings between the signals to be handled and the corresponding exception to be thrown.
A program (or thread) must begin an exception context prior to calling e4c_context_get_signal_mappings
. Such programming error will lead to an abrupt exit of the program (or thread).
bool e4c_context_is_ready | ( | void | ) |
Checks if the current exception context is ready.
This function returns whether there is an actual exception context ready to be used by the program or current thread.
void e4c_context_set_signal_mappings | ( | const e4c_signal_mapping * | mappings ) |
Assigns the specified signal mappings to the exception context.
This function assigns an array of mappings between the signals to be handled and the corresponding exception to be thrown.
A program (or thread) must begin an exception context prior to calling e4c_context_set_signal_mappings
. Such programming error will lead to an abrupt exit of the program (or thread).
Note that the behavior of signal
is undefined in a multithreaded program, so use the signal handling system with caution.
mappings | The array of mappings |
const e4c_exception* e4c_get_exception | ( | void | ) |
Returns the exception that was thrown.
This function returns a pointer to the exception that was thrown in the surrounding try/catch/finally
block, if any, otherwise NULL
.
A program (or thread) must begin an exception context prior to calling e4c_get_exception
. Such programming error will lead to an abrupt exit of the program (or thread).
The function e4c_is_instance_of
will determine if the thrown exception is an instance of any of the defined exception types. The type
of the thrown exception can also be compared for an exact type match.
try{ ... }catch(RuntimeException){ const e4c_exception * exception = e4c_get_exception(); if( e4c_is_instance_of(exception, SignalException.type) ){ ... }else if(exception->type == NotEnoughMemoryException.type){ ... } }
The thrown exception can be obtained any time, provided that the exception context has begun at the time of the function call. However, it is sensible to call this function only during the execution of finally
or catch
blocks.
Moreover, a pointer to the thrown exception obtained inside a finally
or catch
block must not be used from the outside.
The exception system objects are dinamically allocated and deallocated, as the program enters or exits try/catch/finally
blocks. While it is legal to copy the thrown exception and access its name and message outside these blocks, care must be taken in order not to dereference the cause
of the exception, unless it is a deep copy (as opposed to a shallow copy).
NULL
e4c_status e4c_get_status | ( | void | ) |
Returns the completeness status of the executing code block.
Code blocks aware of exceptions have a completeness status regarding the exception handling system. This status determines whether an exception was actually thrown or not, and whether the exception was caught or not.
A program (or thread) must begin an exception context prior to calling e4c_get_status
. Such programming error will lead to an abrupt exit of the program (or thread).
The status of the current block can be obtained any time, provided that the exception context has begun at the time of the function call. However, it is sensible to call this function only during the execution of finally
blocks.
bool e4c_is_instance_of | ( | const e4c_exception * | instance, |
const e4c_exception * | type | ||
) |
Returns whether an exception is of a given exception type.
e4c_is_instance_of
can be used to determine if a thrown exception is an instance of a given type defined through E4C_DEFINE_EXCEPTION
and/or declared through E4C_DECLARE_EXCEPTION
This macro is intended to be used in a catch
block, or in a finally
block provided that some exception was actually thrown (i.e. e4c_get_status
returned e4c_failed
or e4c_recovered
)
try{ ... }catch(RuntimeException){ const e4c_exception * exception = e4c_get_exception(); if( e4c_is_instance_of(exception, SignalException.type) ){ ... }else if(exception->type == NotEnoughMemoryException.type){ ... } }
instance | The thrown exception |
type | A previously defined type of exception |
NullPointerException | if either instance or type is NULL |
long e4c_library_version | ( | void | ) |
Gets the library version number.
This function provides the same information as the E4C_VERSION_NUMBER
macro, but the returned version number is associated with the actual, compiled library.
This version number can be considered as the run-time library version number, as opposed to the compile-time library version number (specified by the header file).
The library must be compiled with the corresponding header (i.e. library version number should be equal to header version number).
void e4c_print_exception | ( | const e4c_exception * | exception ) |
Prints a fatal error message regarding the specified exception.
This is a convenience function for showing an error message through the standard error output. It can be passed to e4c_context_begin
or e4c_using_context
as the handler for uncaught exceptions.
In absence of NDEBUG
, this function prints as much information regarding the exception as it is available, whereas in presence of NDEBUG
, only the name and message of the exception are printed.
exception | The uncaught exception |
NullPointerException | if exception is NULL |
const e4c_exception AbortException |
This exception is thrown to abort the process.
AbortException represents SIGABRT
, the signal sent by computer programs to abort the process.
This exception is thrown when the process performs an erroneous arithmetic operation.
ArithmeticException represents SIGFPE
, the signal sent to a process when it performs an erroneous arithmetic operation.
This exception is thrown when an assertion does not hold.
This exception is part of the assertion facility of the library. It will be thrown if the compile-time parameter NDEBUG
is present and the conditon of an assertion evaluates to false
.
This exception cannot be caught whatsoever. The program (or current thread) will be terminated, after the execution of all pending finally
blocks.
This exception is thrown when the process tries to dereference an invalid pointer.
BadPointerException represents SIGSEGV
, the signal sent to a process when it makes an invalid memory reference, or segmentation fault.
This exception is thrown when the process attempts to write to a broken pipe.
BrokenPipeException represents SIGPIPE
, the signal sent to a process when it attempts to write to a pipe without a process connected to the other end.
This exception is the common supertype of all control signal exceptions.
Control signal exceptions are thrown when the process needs to be controlled by the user or some other process.
This is the hierarchy of control signal exceptions:
This exception is thrown when the process has used up the CPU for too long.
CPUTimeException represents SIGXCPU
, the signal sent to a process when it has used up the CPU for a duration that exceeds a certain predetermined user-settable value.
The array of predefined signal mappings.
This exception is the common supertype of all error signal exceptions.
Error signal exceptions are thrown when some error prevents the program to keep executing its normal flow, for example:
This is the hierarchy of error signal exceptions:
This exception is thrown when a file cannot be opened.
FileOpenException is thrown by e4c_using_file
when fopen
returns NULL
for whatever reason.
The specific cause of the error can be determined by checking the error_number
of the exception; it captures the value of errno
at the time the exception was thrown (right after fopen
).
const e4c_exception HangUpException |
This exception is thrown when the process' terminal is closed.
HangUpException represents SIGHUP
, the signal sent to a process when its controlling terminal is closed.
This exception is thrown when the process attempts to execute an illegal instruction.
IllegalInstructionException represents SIGILL
, the signal sent to a process when it attempts to execute a malformed, unknown, or privileged instruction.
const e4c_exception KillException |
This exception is thrown to terminate the process immediately.
KillException represents SIGKILL
, the signal sent to a process to cause it to terminate immediately.
Since SIGKILL
is unblock-able, it cannot be handled and converted to this exception automatically.
This exception is thrown when the system runs out of memory.
NotEnoughMemoryException is thrown when there is not enough memory to continue the execution of the program.
This exception is thrown when an unexpected null pointer is found.
NullPointerException is thrown when some part of the program gets a pointer which was expected or required to contain a valid memory address.
This exception is thrown when user-defined conditions occur.
ProgramSignal1Exception represents SIGUSR1
, the signal sent to a process to indicate user-defined conditions.
This exception is thrown when user-defined conditions occur.
ProgramSignal2Exception represents SIGUSR1
, the signal sent to a process to indicate user-defined conditions.
This exception is the common supertype of all user-defined signal exceptions.
User-defined signal exceptions are thrown to indicate user-defined conditions.
This is the hierarchy of user-defined signal exceptions:
This is the root of the exception pseudo-hierarchy.
RuntimeException is the common supertype of all exceptions.
This is the hierarchy of runtime exceptions:
This exception is thrown when a time limit has elapsed.
SignalAlarmException represents SIGALRM
, the signal sent to a process when a time limit has elapsed.
This exception is thrown when a child process terminates.
SignalChildException represents SIGCHLD
, the signal sent to a process when a child process terminates (ignored by default).
const e4c_exception SignalException |
This exception is the common supertype of all signal exceptions.
Signal exceptions are thrown when some signal is sent to the process.
A signal can be generated by calling raise
.
This is the hierarchy of signal exceptions:
This exception is thrown when a condition arises that a debugger has requested to be informed of.
SignalTrapException represents SIGTRAP
, the signal sent to a process when a condition arises that a debugger has requested to be informed of.
const e4c_exception StopException |
This exception is thrown to stop the process for later resumption.
StopException represents SIGSTOP
, the signal sent to a process to stop it for later resumption.
Since SIGSTOP
is unblock-able, it cannot be handled and converted to this exception automatically.
This exception is thrown to request the termination of the process.
TerminationException represents SIGTERM
, the signal sent to a process to request its termination.
This exception is thrown when a user wishes to break the process.
UserBreakException represents SIGBREAK
, the signal sent to a process by its controlling terminal when a user wishes to break it.
This exception is the common supertype of all control signal exceptions caused by the user.
User control signal exceptions are thrown when the process needs to be controlled by the user.
This is the hierarchy of control signal exceptions caused by the user:
This exception is thrown when the user requests to interrupt the process.
UserInterruptionException represents SIGINT
, the signal sent to a process by its controlling terminal when a user wishes to interrupt it.
This exception is thrown when the user requests to quit the process.
UserQuitException represents SIGQUIT
, the signal sent to a process by its controlling terminal when the user requests that the process dump core.