exceptions4c-lite 1.0
Lightweight exception handling for C
Loading...
Searching...
No Matches
exceptions4c-lite

Introduction

Bring the power of exceptions to your C applications with this header-only library!

Note
This library provides you with a set of macros and functions that map the exception handling semantics you are probably already used to.

Getting Started

Adding Exceptions to Your Project

This library consists of one header file only. All you need to do is copy exceptions4c-lite.h into your project, include it, and define a global variable exceptions4c.

struct e4c_context exceptions4c = {0};
Really lightweight exception handling for C.
struct e4c_context exceptions4c
Contains the current status of exceptions.

Since it's a header-only library, there is no library code to link against.

Note
This is the lightweight version of exceptions4c. You may also want to try the full version of the library on GitHub.

Defining Exception Types

Create meaningful exceptions that reflect problematic situations in the program.

const e4c_exception_type NOT_ENOUGH_MEMORY = "Not enough memory";
const e4c_exception_type PET_NOT_FOUND = "Pet not found";

An exception type is tied to its default error message.

Usage

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:

Throwing 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.

Throwing Exceptions with a Custom Message

Use THROW to trigger an exception when something goes wrong.

/* Returns a pet by id */
Pet pet_find(int id) {
Pet pet = pet_clone(id);
if (!pet) {
THROW(PET_NOT_FOUND, "Oh no");
}
return pet;
}

Throwing Exceptions with a Default Message

If you don't provide an error message, the default one for that exception type will be used.

/* Returns a pet by id */
Pet pet_find(int id) {
Pet pet = pet_clone(id);
if (!pet) {
THROW(PET_NOT_FOUND, NULL);
}
return pet;
}

Throwing Exceptions with a Formatted Message

Use THROWF to trigger an exception with a formatted message, just as you would with printf.

/* Returns a pet by id */
Pet pet_find(int id) {
Pet pet = pet_clone(id);
if (!pet) {
THROWF(PET_NOT_FOUND, "Pet %d not found", id);
}
return pet;
}

Trying Risky Code

Use a TRY block to wrap code that might cause an exception.

/* Returns the status of a pet by id */
pet_status get_pet_status(int id) {
pet_status status = ERROR;
TRY {
status = pet_find(id)->status;
}
return status;
}

These code blocks, by themselves, don't do anything special. But they allow the introduction of other blocks that do serve specific purposes.

Remarks
A single TRY block must be followed by one or more CATCH blocks to handle the errors, and an optional FINALLY block to execute cleanup code.
Attention
Never exit these blocks using goto, break, continue, or return.

Catching Exceptions

To prevent the program from crashing, exceptions need to be handled properly in designated sections of the code.

Handling Specific Types of Exceptions

Use a CATCH block to handle a specific type of exceptions when they occur.

/* Returns the status of a pet by id */
pet_status get_pet_status(int id) {
pet_status status = ERROR;
TRY {
status = pet_find(id)->status;
} CATCH (PET_NOT_FOUND) {
status = UNKNOWN;
} CATCH (NOT_ENOUGH_MEMORY) {
abort();
}
return status;
}

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.

Handling All Kinds of Exceptions

On the other hand, the CATCH_ALL block is a special block that can handle all types of exceptions.

/* Returns the status of a pet by id */
pet_status get_pet_status(int id) {
pet_status status = ERROR;
TRY {
status = pet_find(id)->status;
status = UNKNOWN;
}
return status;
}

Only one CATCH_ALL block is allowed per TRY block, and it must appear after all type-specific CATCH blocks if any are present.

Retrieving the Current Exception

Use EXCEPTION to retrieve the exception currently being handled.

/* Returns the status of a pet by id */
pet_status get_pet_status(int id) {
pet_status status = ERROR;
TRY {
status = pet_find(id)->status;
if (EXCEPTION.type == NOT_ENOUGH_MEMORY) {
abort();
}
status = UNKNOWN;
}
return status;
}
Remarks
This allows for inspection and further handling of the error, based on both its type and the detailed context of the situation.

Ensuring Cleanup

A FINALLY block always runs, no matter whether an exception happens or not.

/* Returns the status of a pet by id */
pet_status get_pet_status(int id) {
pet_status status = ERROR;
Pet pet = NULL;
TRY {
pet = pet_find(id);
status = pet->status;
} CATCH (PET_NOT_FOUND) {
status = UNKNOWN;
} FINALLY {
pet_free(pet);
}
return status;
}

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.

Remarks
Use EXCEPTION_IS_UNCAUGHT to determine whether the thrown exception hasn't been handled yet.

Customization

You can define these macros to customize exceptions4c-lite:

  • NDEBUG: Determines whether exceptions should retain file/line information for debugging purposes.
    • By default, NDEBUG is not defined and the debugging info is enabled.
  • EXCEPTIONS4C_MAX_LENGTH: Determines the maximum length of an exception message.
    • By default, messages are arrays of 256 characters.
  • EXCEPTIONS4C_MAX_BLOCKS: Determines the maximum number of TRY blocks that can be nested.
    • By default, up to 32 TRY blocks can be nested in the program.
  • EXCEPTIONS4C_PANIC: Determines what needs to be done in the event of too many nested TRY blocks.
    • By default, an error message is printed and then the program is aborted.
  • EXCEPTIONS4C_TERMINATE: Determines what needs to be done in the event of an uncaught exception.
    • By default, the uncaught exception is printed and then the program terminates with a failure value.
  • THROWF: Define this macro at compile time for legacy ANSI C compilers.

Additional Info

Compatibility

This library should compile in any ANSI C compiler.

Remarks
For a fully-featured exception handling library, consider using the complete version exceptions4c, which is also Open Source. And if you're looking for a cleaner, safer, and more modern approach to error handling that doesn't involve throwing and catching exceptions, you may want to take a look at Result Library.

Caveat

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 the setjmp invocation.

Since each TRY block invokes setjmp, modified local variables in scope must be volatile.

Releases

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.

Latest Release

Source Code

The source code is available on GitHub.

Fork me on GitHub