Try Catch in C

  1. Try-Catch in C
  2. Add Finally to Try-Catch in C

Try-Catch mechanisms are common in many programming languages such as Python, C++, and JavaScript. The general structure is below.

try {
    /*
    Insert some lines of code that will probably give you errors
    */
}
catch {
    /*
    Write some code to handle the errors you're getting.
    */
}

They allow you to write code without having to test each statement. If the program running in the try block reaches an exception, the exception is passed to the catch block.

If the exception matches some exception type, the code inside the catch block is executed. Otherwise, the exception is passed back to the try block.

Try-Catch in C

C does not support exception handling. At least, it does not have any built-in mechanism for it.

This guide will demonstrate a possible solution to provide try-catch functionality in C. It should be noted that the solution is not necessarily complete.

Exception handling systems are not complete and safe without a mechanism to free up memory when the stack has been traversed, and C does not have a garbage collector. We would also probably need to include context managers to free up memory.

This solution does not intend to provide a complete and extensive try-catch mechanism. This concept can technically be used to handle a few exceptions.

We will build the solution incrementally, making updates to the code. We will use two functions provided by C, longjmp and setjmp, which can be obtained from the setjmp.h header file.

We will take a closer look at both functions’ definitions.

int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

setjmp takes a variable of type jmp_buf. When this function is called directly, it returns 0.

longjmp takes two variables, and when longjmp is invoked with the same jmp_buf variable, the setjmp function returns with the same value as the second argument of longjmp (val).

The env variable here is essentially the calling environment, representing the state of registers and the position in the code when the function call is being made. When longjmp is called, the state in the calling environment is copied to the processor, and the value stored in the val argument of longjmp is returned.

For a simple Try-Catch block, the idea is to map the Try statement onto the if statement and the Catch statement will then be the else for the conditional. This is where we can make intelligent use of the fact that setjmp can return different values.

If the function returns 0, then we know that the only piece of code that ran was the code in our TRY block. If the function returns anything else, we need to go into our CATCH block with the same state as when we started.

We can call the longjmp function when we THROW an exception.

As you will see in the code below, we also need to close the TRY block. We create an ENDTRY function that provides the closing part of the do-while block.

This also helps us create multiple TRY statements within the same block. It should be noted that they cannot be nested, as we will be reusing the buf_state variable.

An example of this implementation is below.

#include <stdio.h>
#include <setjmp.h>

#define TRY do { jmp_buf buf_state; if ( !setjmp(buf_state)) {
#define CATCH } else {
#define ENDTRY }} while(0)
#define THROW longjmp(buf_state, 1)

int main() {

    TRY {
        printf("Testing Try statement \n");
        THROW;
        printf("Statement should not appear, as the THROW block has already thrown the exception \n");
    }
    CATCH {
        printf("Got Exception \n");
    }
    ENDTRY;

    return 0;
}

Output:

Testing Try statement
Got Exception

For practical systems, this is not enough. We need to have different types of exceptions.

The above example only supports one type of exception. Once again, we can use the different return values of setjmp.

Instead of using if-else, we will switch this around with a switch-case.

The design is as follows: the TRY statement will use a switch statement, and CATCH will be a macro with a parameter representing the exception type. A condition of each CATCH statement will be that it must close the previous case using a break.

#include <stdio.h>
#include <setjmp.h>

#define TRY do { jmp_buf buf_state; switch(setjmp (buf_state)) { case 0:
#define CATCH(x) break; case x:
#define ENDTRY }} while(0)
#define THROW(x) longjmp(buf_state, x)

#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)

int main() {

    TRY {
        printf("Inside Try statement \n");
        THROW(EXCEPTION2);
        printf("This does not appear as exception has already been called \n");
    }
    CATCH(EXCEPTION1) {
        printf("Exception 1 called \n");
    }
    CATCH(EXCEPTION2) {
        printf("Exception 2 called \n");
    }
    CATCH(EXCEPTION3) {
        printf("Exception 3 called \n");
    }
    ENDTRY;

    return 0;
}

Output:

Inside Try statement
Exception 2 called

Add Finally to Try-Catch in C

We need to add a FINALLY block for a complete functional Try-Catch implementation. The finally block generally executes after the try and catch blocks are done.

It executes regardless of whether or not an exception is thrown.

How would we implement this? The key idea is to use the default case of the switch case to implement the FINALLY block.

However, the switch-case would not run the default case if an exception has been called in a normal case.

We will use a mechanism similar to Duff’s Device. We will essentially be intertwining a switch-case statement with a do-while statement.

The logic for it is something like this.

switch (an expression)
{
    case 0: while (1) {
        // code for case 0
        break;
    case 1:
        // code for case 1
        break;
    }
    default:
        // code for default case
}

We use one of C’s most controversial features: switches not breaking automatically before each case label. Here, the while statement is nested inside the switch-case when a break is called; it exits the while loop and continues traversing through the cases.

In the context of our code (as shown below), the switch cases are all our exceptions, and the default case is in our FINALLY block. Naturally, it will fall to the default case, as the exception code would already have been executed.

This is shown in the code below.

#include <stdio.h>
#include <setjmp.h>

#define TRY do { jmp_buf buf_state; switch(setjmp (buf_state)) { case 0: while(1) {
#define CATCH(x) break; case x:
#define ENDTRY }} while(0)
#define THROW(x) longjmp(buf_state, x)
#define FINALLY break; } default:
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)

int main() {

    TRY {
        printf("Inside Try statement \n");
        THROW(EXCEPTION2);
        printf("This does not appear as exception has already been called \n");
    }
    CATCH(EXCEPTION1) {
        printf("Exception 1 called \n");
    }
    CATCH(EXCEPTION2) {
        printf("Exception 2 called \n");
    }
    CATCH(EXCEPTION3) {
        printf("Exception 3 called \n");
    }
    FINALLY {
        printf("This will always be called! \n");
    }
    ENDTRY;

    return 0;
}

Output:

Inside Try statement
Exception 2 called
This will always be called!

This concludes the guide for making a try-catch system in C. Of course, there are possible memory issues here and a few limitations (such as a lack of support for nested try-catch systems), but this is a functional try-catch implementation in C.

Write for us
DelftStack articles are written by software geeks like you. If you also would like to contribute to DelftStack by writing paid articles, you can check the write for us page.