Some error can be handled with just a conditional statements.
int main() {
int a;
int *p = malloc(sizeof(int));
if (p == NULL) {
printf("Allocation failed\n");
return EXIT_FAILURE;
}
*p = 5;
printf("a: %d\n", *p);
free(p);
return EXIT_SUCCESS;
}
malloc
function returns NULL
if it fails.
EXIT_FAILURE
or 1EXIT_SUCCESS
or 0
Basically the same thing.
Success:
a: 5
Failure:
Allocation failed
with code: 1
assert
macro can be used to check truthiness of a statement. If it's false, program exits early.
int main() {
int x = 2;
int y = 3;
assert(x == y);
return 0;
}
Output:
Assertion failed: (x == y), function main, file main.c, line 9.
assert
has performance penalty, thus it's mostly used for debugging. On the release version, it's simply can be disabled by #define NDEBUG
right above #include <assert.h>
.
#define NDEBUG
#include <assert.h>
errno
works as a global variable (thread-local). So we need to set it to 0
first, to clear out any previous error.
For specific errors, errorno defines different error type. Which can be used to determine error type. Such as EDOM, ERANGE, EINTR, EIO etc.
#include <errno.h>
int main() {
errno = 0;
double res = strtol("999999999999999999999", NULL, 10);
if (errno != 0) {
fprintf(stderr, "Operation error\n");
exit(EXIT_FAILURE);
}
return 0;
}
Output:
Operation error
perror
actually prints the error message. It uses the current error code from errorno
.
#include <stdio.h>
if (errno != 0) {
perror("Error");
exit(EXIT_FAILURE);
}
Output:
Error: Result too large
strerror
returns the error message, it doesn't print by default, and errorno (error code) must be supplied to it explicitly.
#include <string.h>
if (errno != 0) {
puts(strerror(errno));
exit(EXIT_FAILURE);
}
Output:
Result too large
Warning!: The errno
variable is thread-local, meaning each thread has its own separate errno
variable. However, in multi-threaded applications, care should still be taken to check errno
immediately after a function that might set it. Failing to do so can lead to errors being missed or misinterpreted, as other functions might modify errno
within the same thread before it is checked.
Signal is actually, inter-process communication (IPC) mechanism designed to handle specific asynchronous events such as process interruptions, illegal instructions, segmentation faults, or termination requests, which can be used for error handling.
Function signature:
void (*signal(int sig, void (*func)(int)))(int);
signal(signal, handler)
The first parameter of signal function is the signal (signal type). A signal can be raised by the system, interrupts or others. We subscribe to that specific signal.
Some of the signals are:
SIGABRT
- Abnormal termination (Abort).SIGFPE
- Error during an arithmetic operation.SIGILL
- Invalid instruction.SIGINT
- Interrupt.SIGSEGV
- Invalid storage access.SIGTERM
- Terminal request.
A handler is the second parameter of the signal function. Which is responsible for what happens when this signal is encountered. There are some built in handler and we can define our own.
Default handler:
SIG_DFL
- Default signal handling.SIG_IGN
- Signal is ignored.
Custom handler:
void handler(int sig) {
printf("%s\n", strsignal(sig));
exit(EXIT_FAILURE);
}
int main() {
signal(SIGINT, handler);
raise(SIGINT);
return 0;
}
Output:
Interrupt: 2
raise
is used to manually raise a signal.
raise(SIGINT);