5. How would you handle errors and exceptions in a C program?

Advanced

5. How would you handle errors and exceptions in a C program?

Overview

Handling errors and exceptions in C is crucial since it does not have built-in exception handling like higher-level languages such as C# or Java. Instead, C programmers must anticipate and manually manage potential errors through various strategies, such as checking return values and using external libraries for more complex error handling. Effective error handling ensures reliability and stability of C programs in critical applications.

Key Concepts

  1. Return Codes: Functions often return specific values to indicate success or different types of errors.
  2. Errno: A global variable set by system calls and some library functions in the event of an error.
  3. Signal Handling: A mechanism to handle asynchronous events (such as division by zero or segmentation faults).

Common Interview Questions

Basic Level

  1. What is the errno variable and how is it used in C programs?
  2. How do you check for errors when using the malloc function?

Intermediate Level

  1. How can you handle signals in a C program?

Advanced Level

  1. Discuss strategies for implementing exception handling mechanisms in C similar to higher-level languages.

Detailed Answers

1. What is the errno variable and how is it used in C programs?

Answer: The errno variable is a global variable used by C standard library functions to indicate errors. Functions that may fail set errno to a nonzero value to indicate what went wrong. It's important to set errno to zero before calling a function that may change it, as functions that succeed do not reset it.

Key Points:
- errno is not automatically set to zero by library functions.
- It is declared in errno.h.
- Specific error codes (e.g., EACCES, ENOSPC) provide detailed error information.

Example:

#include <stdio.h>
#include <errno.h>
#include <string.h>

void checkFileOpen() {
    errno = 0; // Reset errno
    FILE *file = fopen("nonexistent.txt", "r");
    if (file == NULL) {
        printf("Error opening file: %s\n", strerror(errno));
    } else {
        // Process file
        fclose(file);
    }
}

2. How do you check for errors when using the malloc function?

Answer: When using malloc, it's essential to check if the return value is NULL. A NULL return value indicates that malloc was unable to allocate the requested memory, which could lead to a program crash if not handled properly.

Key Points:
- Always check the return value of malloc.
- A NULL pointer indicates an error.
- Handle the error by informing the user, freeing allocated resources, or attempting to recover.

Example:

#include <stdio.h>
#include <stdlib.h>

int* allocateIntArray(int size) {
    int* array = (int*)malloc(size * sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(1); // Exit if allocation fails
    }
    return array;
}

3. How can you handle signals in a C program?

Answer: Signals can be handled in C using the signal() function or the more robust sigaction() function. These functions allow you to define custom signal handlers, which are functions that get called when the specified signal is received.

Key Points:
- Important for handling asynchronous events like SIGINT (Ctrl+C).
- signal() has portability issues; sigaction() is preferred for complex signal handling.
- Always check for error returns from signal handling functions.

Example:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signalHandler(int signalNumber) {
    printf("Caught signal %d\n", signalNumber);
}

int main() {
    if (signal(SIGINT, signalHandler) == SIG_ERR) {
        printf("Error in setting signal handler\n");
        return 1;
    }
    while (1) {
        sleep(1); // Simulate work
    }
    return 0;
}

4. Discuss strategies for implementing exception handling mechanisms in C similar to higher-level languages.

Answer: While C does not have built-in exception handling, strategies such as using setjmp and longjmp functions for non-local jumps, structured error handling blocks (similar to try-catch through macros), and error handling frameworks like libunwind can simulate exception handling mechanisms.

Key Points:
- setjmp and longjmp can be used to jump from a deeply nested function back to a point where an error can be handled gracefully.
- Structured error handling can be implemented with macros to mimic try and catch blocks.
- External libraries or frameworks can provide more sophisticated error and exception handling capabilities.

Example:

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

jmp_buf jumpBuffer;

void throwError() {
    longjmp(jumpBuffer, 1); // Jump back with return value 1
}

void exampleFunction() {
    if (setjmp(jumpBuffer) == 0) {
        // This is the "try" block
        throwError(); // This function "throws" an error
    } else {
        // This is the "catch" block
        printf("Caught an error\n");
    }
}

int main() {
    exampleFunction();
    return 0;
}

This guide outlines the foundational knowledge and examples for handling errors and exceptions in C, covering from basic to advanced levels.