Mastering Error Handling in C: Strategies, Code Samples, and Best Practices
This article provides a comprehensive guide to error handling in C, covering error classification, step‑by‑step handling procedures, return‑value and out‑parameter techniques, global errno usage, goto and setjmp/longjmp jumps, signal handling, program termination functions, assertions, and practical encapsulation patterns with full code examples.
Preface
The article summarizes common error‑handling techniques used in embedded C programming and describes the runtime environment for the examples.
1. Error Concepts
1.1 Error Classification
Errors are classified by severity (fatal vs. non‑fatal) and by interaction (user errors vs. internal errors). Fatal errors cannot be recovered and usually result in logging and program termination, while non‑fatal errors are often temporary (e.g., resource shortage) and can be retried after a delay.
1.2 Handling Steps
Typical error handling consists of five stages:
Detect a software error (e.g., division by zero).
Record the error using an indicator such as an integer or struct.
Detect the error by reading the indicator or receiving a report.
Decide how to handle the error (ignore, partially handle, or fully handle).
Recover from or abort the program.
The following C code illustrates these steps:
int func()
{
int bIsErrOccur = 0;
// do something that might invoke errors
if(bIsErrOccur) // Stage 1: error occurred
return -1; // Stage 2: generate error indicator
// ...
return 0;
}
int main(void)
{
if(func() != 0) // Stage 3: detect error
{
// Stage 4: handle error
}
// Stage 5: recover or abort
return 0;
}2. Error Propagation
2.1 Return Values and Out Parameters
C functions usually signal success or failure via return values. Common patterns include checking the result of malloc, getchar, or clock. While simple, this approach can reduce readability, increase code clutter, and provide limited error information.
To convey richer information, functions may return an enum status and use out‑parameters for additional data:
typedef enum {
S_OK,
S_ERROR,
S_NULL_POINTER,
S_ILLEGAL_PARAM,
S_OUT_OF_RANGE,
S_MAX_STATUS
} FUNC_STATUS;
#define RC_NAME(eRetCode) \
((eRetCode) == S_OK ? "Success" : \
(eRetCode) == S_ERROR ? "Failure" : \
(eRetCode) == S_NULL_POINTER ? "NullPointer" : \
(eRetCode) == S_ILLEGAL_PARAM ? "IllegalParas" : \
(eRetCode) == S_OUT_OF_RANGE ? "OutOfRange" : "Unknown"))2.2 Global State Flag (errno)
Many Unix system calls set a global errno variable on failure. The variable is defined in <errno.h> and is thread‑local on modern systems. Typical usage pattern:
if (some_call() == -1) {
// errno now holds the error code
printf("Error: %s
", strerror(errno));
}Before calling a function that may set errno, it is advisable to reset errno = 0 and check it only when the function indicates failure.
2.3 Local Jump (goto)
The goto statement can centralize cleanup code. Example for division by zero:
int main(void) {
int dwFlag = 0;
if (1 == dwFlag) {
RaiseException:
printf("The divisor cannot be 0!
");
exit(1);
}
dwFlag = 1;
double fDividend = 0.0, fDivisor = 0.0;
printf("Enter the dividend: ");
scanf("%lf", &fDividend);
printf("Enter the divisor : ");
scanf("%lf", &fDivisor);
if (0 == fDivisor)
goto RaiseException;
printf("The quotient is %.2lf
", fDividend / fDivisor);
return 0;
}2.4 Non‑Local Jump (setjmp/longjmp)
setjmpsaves the current stack environment; longjmp restores it, acting like a non‑local goto. Example:
jmp_buf gJmpBuf;
void Func1() { printf("Enter Func1
"); }
void Func2() { printf("Enter Func2
"); }
void Func3() { printf("Enter Func3
"); longjmp(gJmpBuf, 3); }
int main(void) {
int dwJmpRet = setjmp(gJmpBuf);
printf("dwJmpRet = %d
", dwJmpRet);
if (dwJmpRet == 0) {
Func1();
Func2();
Func3();
} else {
switch (dwJmpRet) {
case 1: printf("Jump back from Func1
"); break;
case 2: printf("Jump back from Func2
"); break;
case 3: printf("Jump back from Func3
"); break;
default: printf("Unknown Func!
");
}
}
return 0;
}When used across functions, this mechanism can emulate exception handling.
2.5 Signal Handling (signal/raise)
Signals such as SIGFPE indicate asynchronous error conditions. A handler can be installed with signal and triggered with raise:
void fphandler(int dwSigNo) {
printf("Exception is raised, dwSigNo=%d!
", dwSigNo);
}
int main(void) {
if (SIG_ERR == signal(SIGFPE, fphandler)) {
fprintf(stderr, "Fail to set SIGFPE handler!
");
exit(EXIT_FAILURE);
}
double fDividend = 10.0, fDivisor = 0.0;
if (0 == fDivisor) {
raise(SIGFPE);
exit(EXIT_FAILURE);
}
printf("The quotient is %.2lf
", fDividend / fDivisor);
return 0;
}For integer division, the signal may be raised repeatedly; converting the signal to its default handling or using setjmp/longjmp can avoid endless loops.
3. Error Handling Strategies
3.1 Termination (abort/exit)
Fatal errors can end the program with exit (normal termination) or abort (abnormal termination). exit runs registered termination handlers and flushes standard I/O streams, while _Exit and _exit skip handlers and may skip flushing.
int main(void) {
printf("Using exit...
");
printf("This is the content in buffer");
exit(0);
printf("This line will never be reached
");
}Using _exit without flushing can discard buffered output.
3.2 Assertion (assert)
The assert macro checks conditions that should never fail. In debug builds it expands to a call to __assert, which prints file, line, function, and the failed expression before calling abort. Example:
void __assert(const char *assertion, const char *filename, int linenumber, const char *function) {
fprintf(stderr, "[%s(%d)%s] Assertion '%s' failed.
", filename, linenumber,
function ? function : "UnknownFunc", assertion);
abort();
}Assertions are appropriate for checking internal invariants (e.g., non‑NULL pointers) but not for validating user input.
3.3 Encapsulation
To reduce repetitive error‑checking code, wrap system calls in helper functions. Example of a safe fork wrapper:
pid_t Fork(void) {
pid_t pid;
if ((pid = fork()) < 0) {
fprintf(stderr, "Fork error: %s
", strerror(errno));
exit(0);
}
return pid;
}Similarly, a generic error‑printing routine can be built using stdarg.h and syslog for daemon processes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
