🛡️ Error Handling and Safety in C
Your Program’s Safety Net
🎭 The Story: Building a Safety System
Imagine you’re a pilot flying a plane. What happens if something goes wrong?
- Engine warning light? → You check the instruments (errno)
- Altitude too low? → Alarm sounds (assert)
- Flying into a storm? → Dangerous territory (undefined behavior)
- Fuel gauge overflows? → Number confusion (integer overflow)
- Cargo too heavy? → Weight limit exceeded (buffer overflow)
In C programming, error handling is your cockpit’s warning system. Without it, your program crashes without warning. With it, you know exactly what went wrong and where!
📋 What We’ll Learn
graph TD A["Error Handling"] --> B["Error Overview"] A --> C["errno & Reporting"] A --> D["assert Macro"] A --> E["Undefined Behavior"] A --> F["Integer Overflow"] A --> G["Buffer Overflow"]
1️⃣ Error Handling Overview
What Is Error Handling?
Think of it like a doctor checking your health:
- You feel sick → Doctor runs tests → Finds the problem → Gives medicine
In C:
- Code runs → Something fails → We detect it → We respond
Why Do We Need It?
Without error handling:
FILE *f = fopen("missing.txt", "r");
// f is NULL, but we don't check!
fgets(buffer, 100, f); // CRASH! 💥
With error handling:
FILE *f = fopen("missing.txt", "r");
if (f == NULL) {
printf("File not found!\n");
return 1; // Exit safely
}
fgets(buffer, 100, f); // Safe! ✅
The Three Steps
- Try something that might fail
- Check if it worked
- Respond appropriately
Simple Example:
int result = divide(10, 0);
if (result == -1) {
printf("Oops! Can't divide by zero!");
}
2️⃣ errno and Error Reporting
What Is errno?
errno is like a message board where C posts error codes.
Think of it as a hospital triage system:
- Patient arrives → Nurse writes down the problem code
- Doctor reads the code → Knows what’s wrong
#include <errno.h>
#include <string.h>
FILE *f = fopen("ghost.txt", "r");
if (f == NULL) {
printf("Error code: %d\n", errno);
printf("Message: %s\n", strerror(errno));
}
Output:
Error code: 2
Message: No such file or directory
Common errno Values
| Code | Name | Meaning |
|---|---|---|
| 2 | ENOENT | File not found |
| 13 | EACCES | Permission denied |
| 28 | ENOSPC | No space left |
| 12 | ENOMEM | Out of memory |
Using perror() - The Easy Way
FILE *f = fopen("secret.txt", "r");
if (f == NULL) {
perror("Cannot open file");
}
Output:
Cannot open file: No such file or directory
⚠️ Important Rule
Check errno immediately! It changes after each system call.
// WRONG ❌
fopen("file1.txt", "r");
fopen("file2.txt", "r"); // errno now
// shows file2's error!
// RIGHT ✅
fopen("file1.txt", "r");
int saved = errno; // Save it!
3️⃣ The assert Macro
What Is assert?
assert is like a security guard that checks if things are correct.
Real Life Example:
- Guard at bank vault: “Is your ID valid?”
- ID invalid? → STOP! Sound alarm!
- ID valid? → Proceed
#include <assert.h>
void withdraw(int amount) {
assert(amount > 0); // Guard checks!
// ... proceed with withdrawal
}
How assert Works
int age = -5;
assert(age >= 0); // FAILS! Program stops.
Output:
Assertion failed: age >= 0, file main.c, line 5
When to Use assert
✅ Good uses:
// Check impossible conditions
assert(pointer != NULL);
assert(array_size > 0);
assert(index < MAX_SIZE);
❌ Bad uses:
// Don't use for user input!
assert(user_age > 0); // Wrong!
// Use proper checking instead:
if (user_age <= 0) {
printf("Invalid age!\n");
}
Disabling assert in Production
#define NDEBUG // Put BEFORE include!
#include <assert.h>
// Now all asserts are ignored
assert(1 == 2); // Won't stop program!
4️⃣ Undefined Behavior
What Is Undefined Behavior?
It’s like driving on a road with no map. Anything can happen!
graph TD A["Undefined Behavior"] --> B["Program crashes"] A --> C["Wrong results"] A --> D["Works... sometimes"] A --> E["Hacker takes over!"]
Common Examples
1. Using uninitialized variables:
int x; // Not initialized!
printf("%d", x); // What prints? 🤷
2. Accessing freed memory:
int *p = malloc(sizeof(int));
free(p);
*p = 42; // DANGER! Memory freed!
3. Array out of bounds:
int arr[5];
arr[10] = 99; // Beyond array! 💣
4. Dividing by zero:
int result = 10 / 0; // Undefined!
The Scary Truth
Undefined behavior is like a time bomb:
- Might work today
- Crash tomorrow
- Format your disk next week (theoretically!)
How to Avoid It
| Problem | Solution |
|---|---|
| Uninitialized vars | Always initialize! |
| Freed memory | Set pointer to NULL |
| Array bounds | Check index first |
| Division by zero | Check divisor ≠ 0 |
// Safe code example
int arr[5] = {0}; // Initialize!
int index = 3;
if (index >= 0 && index < 5) {
arr[index] = 42; // Safe! ✅
}
5️⃣ Integer Overflow
What Is Integer Overflow?
Imagine a car odometer that only shows 99999 km.
- Drive 1 more km → Shows 00000!
- The number “wrapped around”
#include <limits.h>
int big = INT_MAX; // 2147483647
printf("%d\n", big); // 2147483647
printf("%d\n", big + 1);// -2147483648 😱
How It Happens
graph LR A["INT_MAX"] -->|+1| B["-INT_MAX-1"] B -->|+1| C["-INT_MAX"] style A fill:#90EE90 style B fill:#FF6B6B
Real World Danger
Bank Example:
int balance = 2000000000; // $2 billion
int deposit = 500000000; // $500 million
int total = balance + deposit;
// Expected: 2.5 billion
// Actual: -1794967296! (Overflow!)
Prevention Techniques
1. Check before operation:
#include <limits.h>
int safe_add(int a, int b) {
if (b > 0 && a > INT_MAX - b) {
return -1; // Would overflow!
}
if (b < 0 && a < INT_MIN - b) {
return -1; // Would underflow!
}
return a + b; // Safe!
}
2. Use larger types:
long long big_number = 2147483647LL;
big_number += 1; // Works fine!
3. Use unsigned for positive-only:
unsigned int count = 0;
// Can hold 0 to 4,294,967,295
6️⃣ Buffer Overflow Prevention
What Is Buffer Overflow?
Imagine pouring 10 liters into a 5-liter bottle. Where does the extra go? It spills everywhere!
In C, that “spill” overwrites other memory. Very dangerous!
char name[5]; // Room for 5 chars
strcpy(name, "Alexander"); // 9 chars! 💥
// Overwrites other memory!
Why It’s Dangerous
Buffer overflow can:
- Crash your program
- Corrupt data
- Let hackers run their own code!
graph TD A["Buffer Overflow"] --> B["Memory corruption"] B --> C["Program crash"] B --> D["Security exploit"] B --> E["Data loss"]
Prevention Methods
1. Use safe functions:
| Dangerous ❌ | Safe ✅ |
|---|---|
| gets() | fgets() |
| strcpy() | strncpy() |
| sprintf() | snprintf() |
| scanf(“%s”) | scanf(“%10s”) |
2. Always specify limits:
char buffer[10];
// WRONG ❌
gets(buffer); // Never use!
// RIGHT ✅
fgets(buffer, 10, stdin);
3. Use strncpy correctly:
char dest[10];
char *src = "Hello World!";
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Ensure null!
4. Use snprintf for formatting:
char msg[20];
int age = 25;
// WRONG ❌
sprintf(msg, "Age: %d years old", age);
// RIGHT ✅
snprintf(msg, sizeof(msg), "Age: %d", age);
5. Validate all input lengths:
void process_name(char *input) {
if (strlen(input) >= MAX_NAME) {
printf("Name too long!\n");
return;
}
// Safe to proceed...
}
🎯 Quick Summary
| Topic | Key Point | Remember |
|---|---|---|
| Error Overview | Check return values | Always verify! |
| errno | Error code storage | Check immediately |
| assert | Debug-time checks | Not for user input |
| Undefined | Unpredictable results | Initialize everything |
| Integer Overflow | Numbers wrap around | Check before math |
| Buffer Overflow | Memory spill | Use safe functions |
🚀 Your Safety Checklist
Before running your C code, ask yourself:
✅ Did I check all return values? ✅ Did I handle NULL pointers? ✅ Did I initialize all variables? ✅ Did I use safe string functions? ✅ Did I check array bounds? ✅ Did I verify numeric operations?
Remember: A few extra checks today save hours of debugging tomorrow!
🎓 Final Thought
“An ounce of prevention is worth a pound of cure.”
In C programming, error handling isn’t optional—it’s essential.
Your program is only as strong as its weakest error check. Make every check count!
Happy (and safe) coding! 🛡️
