Error Handling & Defensive Programming
ভুল মানতে রাজি হবে না এমন কোড লেখা
1. C Has No Exceptions
C-তে exception নেই। Error-এর খবর তিনটি উপায়ে আসে — return code, output parameter, এবং global errno। প্রতিটি library call-এর ফলাফল যাচাই করা আপনার দায়িত্ব।
2. Return Codes
#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
#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
#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
#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;
}
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
- 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; } - Build an
enum Errorwith 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; } - Use
perrorto produce useful error messages.perrorদিয়ে পরিষ্কার error message বানান।✨ Show Answer
Section 3-এর
errno_demo.c-ই উত্তর — প্রতিটি failing call-এর আগে context লিখুন:perror("open /etc/config")। - 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; } - 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। - Use
_Static_assertto 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) ব্যবহার করা যায়। - 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 && ./ofSanitizer output:
runtime error: signed integer overflow— বিস্তারিত file ও line সহ। - 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"।
- When should you use
assertvs 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 হলো না)। - What should
mainreturn 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-ই দেখে। - Wrap
mallocinxmallocthat 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-তে নয়।
- 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 (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Error Handling | Detecting and responding to failure conditions. | Failure ধরা ও তার প্রতিক্রিয়া। |
| Defensive Programming | Writing code that resists bad inputs and partial failures. | খারাপ input ও আংশিক failure-এর বিরুদ্ধে দাঁড়াতে পারা কোড। |
errno | Global variable holding the last system-call error code. | সর্বশেষ system-call error code-এর global variable। |
perror | Prints a description of the current errno. | errno-এর বর্ণনা print করে। |
strerror | Returns a string for an error code. | Error code-এর জন্য string ফেরত দেয়। |
| Return Code | Integer value indicating success/failure of a function. | Function-এর সফলতা/ব্যর্থতা বোঝানো integer। |
| Sentinel Value | A special value (e.g., -1, NULL) signaling no result. | "ফলাফল নেই" বোঝানো বিশেষ মান। |
assert | Macro that aborts if its condition is false. | শর্ত মিথ্যা হলে abort করা macro। |
_Static_assert | Compile-time assertion (C11). | Compile-time assertion (C11)। |
| Invariant | A condition that must always hold at a given point. | একটি পয়েন্টে সর্বদা সত্য থাকা শর্ত। |
| Fail Fast | Stop on first error rather than continuing in a bad state. | প্রথম error-এই থেমে যাওয়া। |
| Goto-Cleanup Pattern | Single cleanup label to free resources on multi-step failure. | Multi-step failure-এ resource cleanup-এর জন্য single label। |
| Logging | Recording 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।