Error Handling & Defensive Programming

ভুল মানতে রাজি হবে না এমন কোড লেখা

~30 min Intermediate 12 practice problems Live code

1. C Has No Exceptions

C-তে exception নেই। Error-এর খবর তিনটি উপায়ে আসে — return code, output parameter, এবং global errno। প্রতিটি library call-এর ফলাফল যাচাই করা আপনার দায়িত্ব।

Java/Python-এ try/catch দিয়ে exception ধরা যায়। C-তে সে সুবিধা নেই — সঠিকতা শতভাগ programmer-এর উপর। তাই প্রতিটি call-এর return value একবার হলেও দেখুন।

2. Return Codes

return_codes.c
#include <stdio.h>

int safe_div(int a, int b, int *out) {
    if (b == 0) return -1;      // error
    *out = a / b;
    return 0;                  // success
}

int main(void) {
    int q;
    if (safe_div(10, 2, &q) == 0) printf("10 / 2 = %d\n", q);
    if (safe_div(10, 0, &q) != 0) puts("10 / 0 failed");
    return 0;
}

Convention: 0 = success, negative বা non-zero = error। প্রতিটি error code কোন অবস্থা নির্দেশ করে, সেটি document করুন।

3. errno, perror, strerror

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

int main(void) {
    FILE *f = fopen("no_such_file.txt", "r");
    if (!f) {
        fprintf(stderr, "errno=%d  detail: %s\n", errno, strerror(errno));
        perror("open failed");
        return 1;
    }
    fclose(f);
    return 0;
}
errno থ্রেড-লোকাল global integer — library call ব্যর্থ হলে সেখানে কারণ সেট হয়। strerror(errno) কারণটিকে মানুষ-পড়ার মতো string-এ অনুবাদ করে, আর perror সেটি সরাসরি stderr-এ print করে।

4. assert — Programmer Errors

assert_demo.c
#include <stdio.h>
#include <assert.h>

int sum_n(int n) {
    assert(n >= 0);                // invariant: must not be negative
    long s = 0;
    for (int i = 1; i <= n; i++) s += i;
    return (int)s;
}

int main(void) {
    printf("sum_n(100) = %d\n", sum_n(100));
    // sum_n(-1);   // would abort with an assertion failure message
    return 0;
}

assert invariant-এর জন্য, যা কখনোই ভুল হওয়া উচিত নয়। -DNDEBUG flag দিয়ে compile করলে সব assert সরিয়ে দেওয়া যায় (release build-এ)। User input-এর validation-এ assert ব্যবহার করবেন না — ওটা প্রত্যাশিত error, bug নয়।

5. Static Assertions (C11)

_Static_assert(sizeof(int) >= 4, "need at least 32-bit int");

Compile-time-এ চেক হয় — runtime cost শূন্য। Build fail হবে যদি condition মেলে না।

6. The goto-Cleanup Pattern

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

int do_work(void) {
    FILE *f = NULL;  char *buf = NULL;  int rc = -1;

    f = fopen("/tmp/ok.txt", "w+");
    if (!f)   goto done;

    buf = malloc(1024);
    if (!buf) goto done;

    snprintf(buf, 1024, "Hello, %s!", "Bangladesh");
    fputs(buf, f);

    rc = 0;
done:
    free(buf);
    if (f) fclose(f);
    remove("/tmp/ok.txt");
    return rc;
}

int main(void) {
    printf("do_work() = %d\n", do_work());
    return 0;
}
একাধিক allocation ও file handle থাকলে goto done-এর মাধ্যমে এক জায়গায় cleanup রাখা Linux kernel-এ প্রচলিত pattern। কোড পরিষ্কার থাকে এবং রিসোর্স leak হয় না।

7. Undefined Behavior — Avoid It

  • Signed integer overflow
  • Dereferencing NULL or wild pointers
  • Out-of-bounds array access
  • Using an uninitialized variable
  • Violating strict aliasing
  • Modifying a variable twice between sequence points

UB হলে anything may happen — crash, wrong output, বা আপাতদৃষ্টিতে "কাজ করা"। Development-এ সবসময় -Wall -Wextra -fsanitize=address,undefined চালান।

8. Practice Problems

  1. Rewrite a function so that every library call's return value is checked.
    প্রতিটি library call-এর return value চেক হয় — এমনভাবে একটি function আবার লিখুন।
    ✨ Show Answer
    check_all.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int save_number(const char *path, int n) {
        FILE *f = fopen(path, "w");
        if (!f) return -1;
        if (fprintf(f, "%d\n", n) < 0) { fclose(f); return -2; }
        if (fclose(f) != 0)          return -3;
        return 0;
    }
    
    int main(void) {
        int rc = save_number("num.txt", 42);
        printf("save_number returned %d\n", rc);
        remove("num.txt");
        return 0;
    }
  2. Build an enum Error with named codes and use it in a module.
    Named error code-এর একটি enum বানান ও ব্যবহার করুন।
    ✨ Show Answer
    err_enum.c
    #include <stdio.h>
    
    typedef enum {
        E_OK = 0,
        E_BAD_ARG,
        E_NOT_FOUND,
        E_IO
    } Error;
    
    const char *err_name(Error e) {
        switch (e) {
            case E_OK:        return "OK";
            case E_BAD_ARG:   return "invalid argument";
            case E_NOT_FOUND: return "not found";
            case E_IO:        return "I/O error";
        }
        return "unknown";
    }
    
    int main(void) {
        Error e = E_NOT_FOUND;
        printf("e = %d (%s)\n", e, err_name(e));
        return 0;
    }
  3. Use perror to produce useful error messages.
    perror দিয়ে পরিষ্কার error message বানান।
    ✨ Show Answer

    Section 3-এর errno_demo.c-ই উত্তর — প্রতিটি failing call-এর আগে context লিখুন: perror("open /etc/config")।

  4. Add assertions to a binary search to encode its invariants.
    Binary search-এ invariant হিসেবে assert যোগ করুন।
    ✨ Show Answer
    bs_assert.c
    #include <stdio.h>
    #include <assert.h>
    
    int bs(const int *a, int n, int key) {
        assert(a != NULL);
        assert(n >= 0);
        int lo = 0, hi = n - 1;
        while (lo <= hi) {
            assert(lo >= 0 && hi < n);      // invariant
            int m = lo + (hi - lo) / 2;
            if (a[m] == key) return m;
            if (a[m] < key) lo = m + 1;
            else            hi = m - 1;
        }
        return -1;
    }
    
    int main(void) {
        int a[] = {1, 3, 7, 12, 19, 25, 42};
        printf("index of 19 = %d\n", bs(a, 7, 19));
        return 0;
    }
  5. Refactor a function with 3 cleanups into the goto-done pattern.
    ৩টি cleanup-যুক্ত function-কে goto-done pattern-এ রূপান্তর করুন।
    ✨ Show Answer

    Section 6-এর goto_cleanup.c-ই reference। Rule: সব cleanup variable শুরুতে NULL/-1-এ init করুন, তারপর done: label-এ সমস্ত cleanup।

  6. Use _Static_assert to verify a struct size at compile time.
    _Static_assert দিয়ে compile-time-এ struct-এর size যাচাই করুন।
    ✨ Show Answer
    size_check.c
    #include <stdio.h>
    
    typedef struct { int a; int b; } Pair;
    
    _Static_assert(sizeof(Pair) == 8, "Pair must be 8 bytes");
    
    int main(void) { printf("sizeof(Pair) = %zu\n", sizeof(Pair)); return 0; }

    _Static_assert বদলে নতুন কোডে static_assert (C23) ব্যবহার করা যায়।

  7. Produce deliberate UB (signed overflow) and run with -fsanitize=undefined.
    Signed overflow দিয়ে UB তৈরি করুন এবং -fsanitize=undefined দিয়ে চালান।
    ✨ Show Answer
    #include <stdio.h>
    #include <limits.h>
    int main(void) {
        int x = INT_MAX;
        printf("%d\n", x + 1);          // signed overflow — UB
        return 0;
    }
    
    // Compile locally:
    // gcc -fsanitize=undefined overflow.c -o of && ./of

    Sanitizer output: runtime error: signed integer overflow — বিস্তারিত file ও line সহ।

  8. Why is a program crashing immediately better than silently producing wrong results?
    ভুল নীরবে চলতে দেওয়ার চেয়ে সাথে সাথে crash করা কেন ভালো?
    ✨ Show Answer

    Crash তৎক্ষণাৎ বাগের অবস্থান জানিয়ে দেয় — stack trace, variable state দেখে ঠিক করা সহজ। নীরব ভুল ডেটাবেজ/ফাইল corrupt করতে পারে, কয়েক দিন পর প্রকাশ পায় — তখন মূল cause খুঁজে বের করা অনেক কঠিন। তাই "fail fast, fail loud, fail early"।

  9. When should you use assert vs return an error code?
    assert কখন, return code কখন?
    ✨ Show Answer

    assert: programmer error/invariant — যা কখনোই false হওয়া উচিত নয় (NULL argument যেখানে accept করা হয় না, index সীমা ভেঙে গেলে)।
    Return code: expected failure — user/filesystem/network-ভিত্তিক সমস্যা (file নেই, memory পাওয়া যায়নি, input parse হলো না)।

  10. What should main return on error? What do shells do with that value?
    Error হলে main কী return করবে? Shell সেটা দিয়ে কী করে?
    ✨ Show Answer

    Convention: 0 = success, 1 বা বেশি = error (common: 1 generic, 2 usage, 64–78 POSIX sysexits.h)। Shell script-এ if ./prog; then ... বা ./prog && echo ok || echo failed — exit code অনুযায়ী শাখাবিভক্তি। CI/CD-ও exit code-ই দেখে।

  11. Wrap malloc in xmalloc that exits on failure — pros and cons.
    malloc-কে xmalloc-এ wrap করুন যা fail হলে exit করে — সুবিধা ও অসুবিধা।
    ✨ Show Answer
    xmalloc.c
    #include <stdio.h>
    #include <stdlib.h>
    
    void *xmalloc(size_t n) {
        void *p = malloc(n);
        if (!p) { fputs("out of memory\n", stderr); exit(1); }
        return p;
    }
    
    int main(void) {
        int *a = xmalloc(10 * sizeof *a);
        for (int i = 0; i < 10; i++) a[i] = i * i;
        printf("a[3] = %d\n", a[3]);
        free(a);
        return 0;
    }

    সুবিধা: সব জায়গায় NULL check করতে হয় না — কোড পরিষ্কার। অসুবিধা: library code-এ exit() অনুচিত — caller recovery-র সুযোগ পায় না। CLI program-এ উপযোগী, library-তে নয়।

  12. Review any of your earlier programs and find one place you skipped a return-value check. Fix it.
    আগের কোনো প্রোগ্রাম পর্যালোচনা করে একটি জায়গা খুঁজুন যেখানে return value চেক হয়নি — ঠিক করুন।
    ✨ Show Answer

    Common targets: scanf, fopen, malloc, fgets, fread, fclose, snprintf। প্রতিটির signature ও return value MDN/man page-এ দেখে নিন, এরপর প্রোগ্রামে if (... != expected) { ... } যোগ করুন।

Glossary (শব্দকোষ)

TermMeaningবাংলায়
Error HandlingDetecting and responding to failure conditions.Failure ধরা ও তার প্রতিক্রিয়া।
Defensive ProgrammingWriting code that resists bad inputs and partial failures.খারাপ input ও আংশিক failure-এর বিরুদ্ধে দাঁড়াতে পারা কোড।
errnoGlobal variable holding the last system-call error code.সর্বশেষ system-call error code-এর global variable।
perrorPrints a description of the current errno.errno-এর বর্ণনা print করে।
strerrorReturns a string for an error code.Error code-এর জন্য string ফেরত দেয়।
Return CodeInteger value indicating success/failure of a function.Function-এর সফলতা/ব্যর্থতা বোঝানো integer।
Sentinel ValueA special value (e.g., -1, NULL) signaling no result."ফলাফল নেই" বোঝানো বিশেষ মান।
assertMacro that aborts if its condition is false.শর্ত মিথ্যা হলে abort করা macro।
_Static_assertCompile-time assertion (C11).Compile-time assertion (C11)।
InvariantA condition that must always hold at a given point.একটি পয়েন্টে সর্বদা সত্য থাকা শর্ত।
Fail FastStop on first error rather than continuing in a bad state.প্রথম error-এই থেমে যাওয়া।
Goto-Cleanup PatternSingle cleanup label to free resources on multi-step failure.Multi-step failure-এ resource cleanup-এর জন্য single label।
LoggingRecording diagnostic information at runtime.Runtime-এ diagnostic record রাখা।

Summary — Module 24

C-তে প্রতিটি call-এর return value আপনিই চেক করবেন। Expected failure-এ return code + errno; invariant-এ assert; compile-time চেকে _Static_assert; multi-resource cleanup-এ goto-done pattern। Fail fast, fail loud, fail early।

Next Module → Linked Lists — Singly, Doubly, Circular।