C Interview Questions and Answers(2025)
Question: How do you handle errors in C?
Answer:
In C, error handling is typically done through a combination of return codes, error messages, and sometimes, specialized error-handling functions. C does not provide built-in exceptions like higher-level languages, so error handling in C is generally done in a more manual way. Below are several common techniques for handling errors in C:
1. Return Codes:
One of the most common approaches to error handling in C is to use return codes from functions. A function returns an integer or another type to indicate success or failure, and the calling function can check the return value to determine whether an error occurred.
Example:
#include <stdio.h>
// Function to divide two numbers, returns -1 if there is an error (e.g., division by zero)
int divide(int a, int b) {
if (b == 0) {
return -1; // Error: division by zero
}
return a / b; // Successful division
}
int main() {
int result = divide(10, 0);
if (result == -1) {
printf("Error: Division by zero\n");
} else {
printf("Result: %d\n", result);
}
return 0;
}
In this example, the divide
function returns -1
to indicate an error (e.g., division by zero). The calling code checks the return value and prints an appropriate error message.
2. errno
and Standard Error Handling:
The C standard library provides a global variable errno
that stores error codes set by system calls or library functions when they fail. Functions like malloc()
, fopen()
, read()
, etc., can set errno
to indicate specific error conditions.
Example:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
printf("Error opening file: %s\n", strerror(errno)); // Use strerror to print error message
} else {
fclose(file);
}
return 0;
}
Key Points:
errno
is set to a specific error code when a function fails (e.g.,ENOENT
for “No such file or directory”).strerror(errno)
can be used to get a human-readable string corresponding to the error code.- It is important to reset
errno
to 0 before making a system call if you need to check it for errors after a previous call.
3. exit()
and abort()
for Critical Errors:
If a function detects a critical error from which it cannot recover, it can terminate the program using exit()
or abort()
.
exit(status)
terminates the program and returns the status to the operating system. Typically,exit(0)
indicates success, while non-zero values indicate failure.abort()
immediately terminates the program, without cleaning up resources or performing any normal shutdown tasks.
Example using exit()
:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
printf("Error: Unable to open file.\n");
exit(1); // Exit the program with a failure status
}
fclose(file);
return 0;
}
Example using abort()
:
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = -1;
if (x < 0) {
printf("Critical error: x cannot be negative.\n");
abort(); // Immediately terminate the program
}
return 0;
}
Key Points:
- Use
exit()
when you want to gracefully terminate the program and potentially clean up resources. - Use
abort()
when you encounter a critical error and need to terminate immediately without cleanup.
4. Custom Error Handling Functions:
You can also write custom error-handling functions to centralize the error handling logic in your program, making it easier to manage errors in a consistent manner.
Example:
#include <stdio.h>
#include <stdlib.h>
// Custom error handler
void handle_error(const char *error_message) {
fprintf(stderr, "Error: %s\n", error_message);
exit(1); // Exit the program with failure status
}
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
handle_error("Unable to open the file.");
}
fclose(file);
return 0;
}
This function centralizes error reporting and can be reused across your program.
5. Assertions (assert()
):
The assert()
macro is used to check conditions during runtime. If the condition is false, it prints an error message and aborts the program. This is typically used during debugging to catch logical errors in the program.
Example:
#include <stdio.h>
#include <assert.h>
int divide(int a, int b) {
assert(b != 0); // Assert that b is not zero
return a / b;
}
int main() {
int result = divide(10, 0); // This will trigger an assertion failure
printf("Result: %d\n", result);
return 0;
}
Key Points:
assert()
is mainly used for debugging and testing. It should be disabled in production code by definingNDEBUG
before includingassert.h
.- If the assertion fails, the program terminates immediately with an error message.
6. Using setjmp()
and longjmp()
for Exception Handling:
While C does not have built-in exception handling (like C++ or Java), you can use setjmp()
and longjmp()
to implement a basic form of exception handling. setjmp()
saves the program’s state at a certain point, and longjmp()
can jump back to that point, allowing you to handle errors more flexibly.
Example:
#include <stdio.h>
#include <setjmp.h>
jmp_buf jump_buffer;
void handle_error() {
printf("Error occurred, jumping back to the safe point.\n");
longjmp(jump_buffer, 1); // Jump back to the point where setjmp() was called
}
int main() {
if (setjmp(jump_buffer) != 0) {
printf("Recovery from error\n");
return 1;
}
printf("Starting program...\n");
handle_error(); // Trigger the error and jump back
printf("This will not be printed.\n");
return 0;
}
Key Points:
setjmp()
saves the program’s state, andlongjmp()
restores it.- This technique can be used to simulate exception handling, but it can be error-prone and is rarely used in C unless necessary.
Conclusion:
Error handling in C is largely manual and can be accomplished using techniques such as return codes, the errno
variable, exit()
, abort()
, assertions, and even custom error-handling functions. Each method has its use case, and the choice depends on the severity of the error and whether recovery is possible. Unlike higher-level languages, C requires you to explicitly check for errors and handle them in a way that fits your program’s needs.
Question: What is the difference between exit()
and _exit()
in C?
Answer:
In C, both exit()
and _exit()
are used to terminate a program, but they differ in how they clean up resources before termination.
Here’s a detailed breakdown of the differences:
1. Function Signature:
exit(int status)
: This is a standard library function defined in<stdlib.h>
. It is the most commonly used function for program termination._exit(int status)
: This is a system call function (often defined in<unistd.h>
in Unix-like systems). It is lower-level compared toexit()
.
2. Behavior Before Terminating the Program:
-
exit()
:- When called,
exit()
performs a clean termination of the program. - It flushes all buffered output to stdout and stderr, ensuring that all data is written out (this is important for programs that have buffered output).
- It closes all open files and performs other necessary clean-up tasks, such as calling any functions registered with
atexit()
. - It calls destructors for objects that were created in C++ (if applicable), though in pure C, there are no destructors for objects, but
exit()
will still clean up other resources. - After all the cleanup actions are completed, it terminates the program by returning control to the operating system, passing the exit status to the OS (usually
status
is passed to the operating system, which is returned to the shell).
Example:
#include <stdio.h> #include <stdlib.h> int main() { printf("Program will terminate cleanly.\n"); exit(0); // Program terminates cleanly }
- When called,
-
_exit()
:_exit()
performs an immediate termination of the program without any cleanup.- It does not flush buffered output to stdout or stderr, meaning that data may not be written if the buffers are not flushed before calling
_exit()
. - It does not close open files or clean up any allocated memory.
- No functions registered with
atexit()
are called, and no destructors are executed (in C++ or C with static objects). - It simply exits the process immediately with the specified status code, providing minimal system-level cleanup.
Example:
#include <stdio.h> #include <unistd.h> int main() { write(1, "Program is terminating abruptly.\n", 32); // Direct write to stdout _exit(1); // Immediate termination without flushing stdout }
3. When to Use:
exit()
is typically used when you want a program to terminate gracefully, ensuring that resources are freed, buffers are flushed, and files are properly closed._exit()
is generally used in low-level situations where you want to exit the program immediately without performing any cleanup. This is common in cases such as in child processes after afork()
system call, where you do not want the child to perform any cleanup that the parent process might have set up.
4. Use in Forked Processes:
- In programs that use
fork()
to create child processes,exit()
is generally used in the parent process or the child process when you want to terminate the program cleanly. - However, in the child process after a
fork()
, if you want to exit immediately without performing cleanup (such as flushing output or closing files),_exit()
is more appropriate, becauseexit()
may cause the parent process’s resources to be unintentionally cleaned up.
Example with fork()
and _exit()
:
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) { // Child process
_exit(0); // Exit immediately, no cleanup
} else { // Parent process
wait(NULL); // Wait for child to terminate
printf("Child process has terminated.\n");
}
return 0;
}
5. Summary of Key Differences:
Feature | exit() | _exit() |
---|---|---|
Cleanup Operations | Performs cleanup (flushes buffers, calls atexit() handlers, closes files) | Does not perform cleanup |
Buffered Output | Flushes buffered output (stdout, stderr) | Does not flush buffered output |
File Descriptors | Closes open files and releases resources | Does not close open files |
atexit Handlers | Calls functions registered with atexit() | Does not call atexit() handlers |
Usage | For normal program termination | For immediate termination, usually in low-level or error scenarios |
Conclusion:
- Use
exit()
when you want to terminate the program in a graceful and clean way, ensuring resources are properly cleaned up. - Use
_exit()
when you need to terminate the program immediately, without performing any cleanup, often in low-level scenarios like after afork()
system call in the child process.
Read More
If you can’t get enough from this article, Aihirely has plenty more related information, such as C interview questions, C interview experiences, and details about various C job positions. Click here to check it out.
Tags
- C Language
- Pointers
- Memory Management
- Data Types
- Structures
- Unions
- Functions
- Function Pointers
- Static Keyword
- C Programming
- C Arrays
- Loops in C
- Memory Allocation
- Malloc
- Calloc
- Segmentation Fault
- Error Handling in C
- Header Files
- Macros in C
- String Handling
- Const Keyword
- C Interview Questions
- C++ vs C
- Pre increment
- Post increment
- C Syntax
- C Debugging
- C Programming Concepts
- Manual Memory Management
- Exit vs exit
- C Programming Techniques