Program Errors

Introduction

Program errors are mistakes or flaws in computer programs that prevent them from functioning correctly. Understanding different types of errors, their causes, and how to identify and fix them is crucial for effective programming. Errors can occur at different stages of program development and execution, each requiring specific approaches for resolution.

Key Concepts

Error: A mistake in a program that produces incorrect results or prevents execution Bug: Common term for program errors Exception: Runtime error that can be handled by the program Fault: Design flaw that may lead to errors under certain conditions Failure: Observable incorrect behavior caused by errors Error Handling: Techniques to manage and recover from errors

Types of Program Errors

1. Syntax Errors (Compile-time Errors)

Missing Semicolons

#include <stdio.h>

int main() {
    int x = 10      // Error: Missing semicolon
    int y = 20;     // Error: Previous line affects this
    
    printf("Sum: %d\n", x + y);
    return 0;
}

// Compiler Error:
// error: expected ';' before 'int'

Incorrect Brackets and Parentheses

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    for (int i = 0; i < 5; i++ {    // Error: Missing closing parenthesis
        printf("%d ", arr[i]);
    }
    
    if (arr[0] > 0 {                // Error: Missing closing parenthesis
        printf("Positive\n");
    }
    
    return 0;
// Error: Missing closing brace for main function

Undeclared Variables

#include <stdio.h>

int main() {
    int x = 10;
    
    printf("Value of y: %d\n", y);  // Error: 'y' undeclared
    
    z = x + 5;                      // Error: 'z' undeclared
    
    return 0;
}

Type Mismatches

#include <stdio.h>

int main() {
    int number;
    char *text = "Hello";
    
    number = text;                  // Error: Cannot assign string to int
    
    int arr[3.5];                   // Error: Array size must be integer
    
    return 0;
}

2. Runtime Errors (Execution Errors)

Division by Zero

#include <stdio.h>

int main() {
    int a = 10;
    int b = 0;
    
    int result = a / b;             // Runtime error: Division by zero
    printf("Result: %d\n", result);
    
    return 0;
}

// Improved version with error checking
int safeDivision() {
    int a = 10;
    int b = 0;
    
    if (b != 0) {
        int result = a / b;
        printf("Result: %d\n", result);
    } else {
        printf("Error: Division by zero attempted\n");
        return -1;
    }
    
    return 0;
}

Array Index Out of Bounds

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    // Valid access
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    // Runtime error: Array index out of bounds
    printf("arr[10] = %d\n", arr[10]);  // Accessing invalid memory
    arr[15] = 100;                      // Writing to invalid memory
    
    return 0;
}

// Safe array access
void safeArrayAccess() {
    int arr[5] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    int index = 10;
    
    if (index >= 0 && index < size) {
        printf("arr[%d] = %d\n", index, arr[index]);
    } else {
        printf("Error: Index %d is out of bounds (0-%d)\n", index, size-1);
    }
}

Null Pointer Dereference

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

int main() {
    int *ptr = NULL;
    
    *ptr = 10;                      // Runtime error: Dereferencing NULL pointer
    printf("Value: %d\n", *ptr);
    
    return 0;
}

// Safe pointer usage
void safePointerUsage() {
    int *ptr = malloc(sizeof(int));
    
    if (ptr != NULL) {
        *ptr = 10;
        printf("Value: %d\n", *ptr);
        free(ptr);
        ptr = NULL;  // Prevent accidental reuse
    } else {
        printf("Error: Memory allocation failed\n");
    }
}

Memory Allocation Failures

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

int main() {
    // Attempting to allocate large amount of memory
    size_t huge_size = SIZE_MAX;
    int *ptr = malloc(huge_size);
    
    *ptr = 10;                      // Runtime error if malloc failed
    printf("Value: %d\n", *ptr);
    
    free(ptr);
    return 0;
}

// Proper memory allocation with error checking
void properMemoryAllocation() {
    size_t size = 1000 * sizeof(int);
    int *ptr = malloc(size);
    
    if (ptr == NULL) {
        fprintf(stderr, "Error: Failed to allocate %zu bytes\n", size);
        return;
    }
    
    // Use the allocated memory
    for (int i = 0; i < 1000; i++) {
        ptr[i] = i;
    }
    
    printf("Memory allocated and used successfully\n");
    
    free(ptr);
}

3. Logic Errors (Semantic Errors)

Incorrect Algorithm Implementation

#include <stdio.h>

// Incorrect implementation of factorial
int wrongFactorial(int n) {
    int result = 0;                 // Should be 1, not 0
    for (int i = 1; i <= n; i++) {
        result += i;                // Should be multiplication, not addition
    }
    return result;
}

// Correct implementation
int correctFactorial(int n) {
    if (n < 0) return -1;           // Handle negative input
    if (n == 0 || n == 1) return 1;
    
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

int main() {
    int num = 5;
    
    printf("Wrong factorial of %d: %d\n", num, wrongFactorial(num));
    printf("Correct factorial of %d: %d\n", num, correctFactorial(num));
    
    return 0;
}

Off-by-One Errors

#include <stdio.h>

void demonstrateOffByOneErrors() {
    int arr[5] = {10, 20, 30, 40, 50};
    
    printf("Wrong loop (off-by-one error):\n");
    for (int i = 1; i <= 5; i++) {          // Should start at 0, not 1
        printf("arr[%d] = %d\n", i, arr[i]); // Will access arr[5] which is out of bounds
    }
    
    printf("\nCorrect loop:\n");
    for (int i = 0; i < 5; i++) {            // Correct bounds
        printf("arr[%d] = %d\n", i, arr[i]);
    }
}

// String processing with off-by-one error
void stringProcessingError() {
    char str[] = "Hello";
    int len = strlen(str);
    
    printf("Wrong string reversal:\n");
    for (int i = 0; i <= len; i++) {         // Should be i < len
        printf("%c", str[len - i]);          // Will access str[-1] and str[5]
    }
    
    printf("\nCorrect string reversal:\n");
    for (int i = len - 1; i >= 0; i--) {     // Correct implementation
        printf("%c", str[i]);
    }
    printf("\n");
}

Infinite Loops

#include <stdio.h>

void demonstrateInfiniteLoop() {
    int i = 10;
    
    // Infinite loop - condition never becomes false
    while (i > 0) {
        printf("Value: %d\n", i);
        i++;                                 // Should be i-- to decrease
        
        // Safety break to prevent actual infinite loop in demo
        if (i > 15) break;
    }
}

// Correct loop implementation
void correctLoop() {
    int i = 10;
    
    while (i > 0) {
        printf("Value: %d\n", i);
        i--;                                 // Correct decrement
    }
    printf("Loop completed\n");
}

4. Linking Errors

Undefined References

// main.c
#include <stdio.h>

// Function declared but not defined
int calculateSum(int a, int b);
void displayResult(int result);

int main() {
    int result = calculateSum(10, 20);      // Linking error: undefined reference
    displayResult(result);                  // Linking error: undefined reference
    return 0;
}

// To fix: Create separate file with implementations
// utils.c
int calculateSum(int a, int b) {
    return a + b;
}

void displayResult(int result) {
    printf("Result: %d\n", result);
}

Multiple Definitions

// file1.c
int globalVariable = 10;                    // Definition

// file2.c  
int globalVariable = 20;                    // Error: Multiple definition

// To fix: Use extern in header and define in one source file
// header.h
extern int globalVariable;

// file1.c
int globalVariable = 10;                    // Single definition

// file2.c includes header.h and uses extern declaration

Error Detection and Prevention

1. Compiler Warnings

#include <stdio.h>

int main() {
    int x = 10;
    float y = 3.14;
    
    int result = x + y;                     // Warning: implicit conversion
    
    if (x = 5) {                            // Warning: assignment in condition
        printf("x is 5\n");
    }
    
    return 0;
}

// Compile with warnings: gcc -Wall -Wextra program.c

2. Static Analysis Tools

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

void demonstrateStaticAnalysisIssues() {
    int *ptr = malloc(sizeof(int));
    *ptr = 10;
    
    // Memory leak - forgot to free
    // Static analysis tools would detect this
    
    int arr[10];
    for (int i = 0; i <= 10; i++) {         // Buffer overflow
        arr[i] = i;                         // Static analysis would warn
    }
    
    int x;
    printf("Uninitialized: %d\n", x);      // Use of uninitialized variable
}

// Corrected version
void correctedVersion() {
    int *ptr = malloc(sizeof(int));
    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    
    *ptr = 10;
    printf("Value: %d\n", *ptr);
    free(ptr);                              // Proper cleanup
    
    int arr[10];
    for (int i = 0; i < 10; i++) {          // Correct bounds
        arr[i] = i;
    }
    
    int x = 0;                              // Proper initialization
    printf("Initialized: %d\n", x);
}

3. Runtime Error Detection

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

jmp_buf jump_buffer;

void segfault_handler(int sig) {
    printf("Caught segmentation fault! Attempting recovery...\n");
    longjmp(jump_buffer, 1);
}

int main() {
    signal(SIGSEGV, segfault_handler);
    
    if (setjmp(jump_buffer) == 0) {
        // Dangerous operation
        int *ptr = NULL;
        *ptr = 10;                          // This will cause segfault
    } else {
        // Recovery path
        printf("Recovered from error, continuing execution\n");
    }
    
    printf("Program completed successfully\n");
    return 0;
}

Error Handling Strategies

1. Return Code Checking

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

int safeFileOperation(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        perror("Error opening file");
        return -1;                          // Error code
    }
    
    char buffer[256];
    while (fgets(buffer, sizeof(buffer), file)) {
        printf("%s", buffer);
    }
    
    if (fclose(file) != 0) {
        perror("Error closing file");
        return -2;                          // Different error code
    }
    
    return 0;                               // Success
}

int main() {
    int result = safeFileOperation("example.txt");
    
    switch (result) {
        case 0:
            printf("File operation successful\n");
            break;
        case -1:
            printf("Failed to open file\n");
            break;
        case -2:
            printf("Failed to close file\n");
            break;
        default:
            printf("Unknown error\n");
    }
    
    return 0;
}

2. Error Logging

#include <stdio.h>
#include <time.h>
#include <stdarg.h>

void logError(const char *format, ...) {
    FILE *logFile = fopen("error.log", "a");
    if (logFile == NULL) return;
    
    time_t now = time(NULL);
    fprintf(logFile, "[%s] ERROR: ", ctime(&now));
    
    va_list args;
    va_start(args, format);
    vfprintf(logFile, format, args);
    va_end(args);
    
    fprintf(logFile, "\n");
    fclose(logFile);
}

int riskyOperation(int value) {
    if (value < 0) {
        logError("Invalid input value: %d", value);
        return -1;
    }
    
    if (value > 1000) {
        logError("Value too large: %d (max: 1000)", value);
        return -2;
    }
    
    return value * 2;
}

int main() {
    int result1 = riskyOperation(-5);
    int result2 = riskyOperation(2000);
    int result3 = riskyOperation(50);
    
    printf("Results: %d, %d, %d\n", result1, result2, result3);
    
    return 0;
}

Best Practices for Error Prevention

  1. Always check return values of functions that can fail
  2. Initialize variables before use
  3. Validate input parameters in functions
  4. Use compiler warnings and treat them seriously
  5. Test boundary conditions and edge cases
  6. Use static analysis tools to catch potential issues
  7. Implement proper error handling and recovery mechanisms
  8. Write unit tests to verify code correctness
  9. Use version control to track changes and revert if needed
  10. Code reviews to catch errors before deployment

Summary

Program errors are classified into syntax errors (caught during compilation), runtime errors (occur during execution), logic errors (produce incorrect results), and linking errors (occur during the linking phase). Understanding these error types and implementing proper error detection, handling, and prevention strategies is essential for writing robust programs. Good programming practices, thorough testing, and appropriate tools help minimize errors and improve code quality.


Part of BCA Programming with C Course (UGCOA22J201)