HOWTO · C
Try Catch in C
This article demonstrates an example of implementing try-catch behavior in C, which by default, does not support try-catch/exception handling mechanisms.
On this page
C has no built-in try-catch or exception handling. You can use setjmp and longjmp from <setjmp.h> to transfer control to a saved point and build a small macro-based TRY, CATCH, and THROW pattern. This is a limited control-flow technique, not a complete exception system: it does not automatically release resources, and a thrown value with no matching CATCH selects no handler in the shown switch design. Each TRY invocation has its own block-scoped buf_state and switch, so nested invocations are supported by this macro layout.
Try-Catch in C
setjmp saves an execution environment in a jmp_buf. When called directly, it returns 0; after a later longjmp to that environment, it returns the supplied value (except that a supplied value of 0 is observed as 1). longjmp restores the saved execution context and continues at the setjmp call. It does not copy state to a processor.
The macros below use a switch: the initial setjmp result selects the TRY body, and each CATCH(value) adds a matching case. THROW(value) calls longjmp. An unmatched nonzero value selects no catch body and is ignored when the switch ends; it is not sent back to the TRY body. The break generated by each CATCH keeps execution from falling through into later cases.
Run this example:
#include <setjmp.h>
#include <stdio.h>
#define TRY do { jmp_buf buf_state; switch (setjmp(buf_state)) { case 0:
#define CATCH(value) break; case value:
#define ENDTRY } } while (0)
#define THROW(value) longjmp(buf_state, value)
#define CODE_ONE 1
#define CODE_TWO 2
int main(void) {
TRY {
puts("Starting work");
THROW(CODE_TWO);
puts("This line is skipped");
}
CATCH(CODE_ONE) { puts("Code one"); }
CATCH(CODE_TWO) { puts("Code two"); }
ENDTRY;
return 0;
}
Starting work
Code two
The supplied run exited with status 0. This complete program illustrates one matched transfer. jmp_buf must remain valid when longjmp is called: jumping after the function containing the matching setjmp has returned is not valid. Also, the values of non-volatile automatic variables local to the function containing setjmp can become indeterminate if they were changed after setjmp and before longjmp. Use this pattern only when those control-flow and lifetime rules are understood.
Add Finally to Try-Catch in C
C has no FINALLY statement, but a finally-like action can be written after a switch inside a do { ... } while (0) wrapper. This compact control-flow shape puts case labels inside the loop, as in a Duff’s Device-style arrangement:
int status = 0; do { switch (setjmp(env)) { case 0: work(); break; case CODE_TWO: handle(); break; default: status = 1; break; } } while (0); cleanup(); return status;
On normal completion, work() finishes and its break exits the switch; the one-iteration loop ends, then execution reaches cleanup(). On a matched throw, longjmp makes setjmp return CODE_TWO, so handle() runs and its break reaches the same cleanup path. On an unmatched nonzero return from setjmp, default runs, records the unmatched outcome, and its break also reaches cleanup. In each case, break exits the switch, not the surrounding function. This is illustrative control flow, not automatic resource cleanup or complete exception semantics.
The runnable example below shows the matched-throw path through a switch and a shared cleanup label:
#include <setjmp.h>
#include <stdio.h>
#define CODE_TWO 2
int main(void) {
jmp_buf env;
switch (setjmp(env)) {
case 0:
puts("Starting work");
longjmp(env, CODE_TWO);
case CODE_TWO:
puts("Handling code two");
break;
default:
puts("No matching handler");
break;
}
cleanup:
puts("Cleanup path");
return 0;
}
Starting work
Handling code two
Cleanup path
The supplied run exited with status 0. In this path, longjmp resumes the switch with CODE_TWO, the handler runs, and execution reaches the cleanup label. The label is an alternative way to make a shared path visible; for the do/while shape above, normal completion, a matched throw, and an unmatched return each need an explicit route to cleanup. longjmp does not free allocated memory, close files, unlock resources, or run destructors. Release such resources explicitly along the paths your program supports.