In C, there is no built-in support for exceptions; error handling is implemented in the following ways:
Returning error codes (usually via the function's return value) Example:
int read_file(const char *name) { FILE *f = fopen(name, "r"); if (!f) return -1; // Error // ... fclose(f); return 0; // Success }
Using the global variable errno
When an error occurs, standard functions often set the global variable errno.
Example:
FILE *fp = fopen("nofile", "r"); if (!fp) { perror("File error"); // errno indicates the reason }
setjmp/longjmp — the throw "exception" mechanism (execution context is saved and restored) Used for complex cases (e.g., cross-level emergency termination). Rarely used because it complicates the code and can lead to resource leaks.
#include <setjmp.h> jmp_buf buf; if (setjmp(buf) == 0) { // ... longjmp(buf, 1); // Jumps to if (setjmp(...)) } else { // Error handling }
What will the following code output?
#include <stdio.h> #include <setjmp.h> jmp_buf buf; void func() { longjmp(buf, 5); } int main() { int val = setjmp(buf); printf("val=%d\n", val); if (val == 0) func(); return 0; }
Answer:
setjmp returns 0 and the function func is called.longjmp(buf, 5) is executed, and execution returns to setjmp, which now returns 5.val=0
val=5
Story In one library, a manual resource cleanup mechanism was implemented on error only based on the return code. However, the programmer forgot to handle an error when returning not from the main thread, but from a callback (via setjmp/longjmp), resulting in memory leaks and file locks.
Story In a network application, the check for
errnoafter a socket operation was missed. As a result, there was a false diagnosis: the variableerrnoretained an old incorrect value from the previous function.
Story The team implemented complex nesting with multiple return code error points but did not adhere to the convention regarding return values (zero/negative values). Some errors were interpreted as successful, leading to unpredictable service failures.