5.1.30 · D4 · HinglishC Programming

ExercisesUndefined behavior — comprehensive list, why to avoid

2,911 words13 min read↑ Read in English

5.1.30 · D4 · Coding › C Programming › Undefined behavior — comprehensive list, why to avoid


Level 1 — Recognition

Goal: ek snippet dekh ke decide karo "UB / defined / unspecified / implementation-defined" aur category ka naam lo.

Recall Solution L1.1
  • (a) Defined. Unsigned arithmetic modulo wrap karta hai (yahan ), toh u ho jaata hai. Yeh ek guarantee hai, luck nahi. Dekho Integer Types and Overflow.
  • (b) UB. Signed integer overflow. INT_MAX + 1 ka koi defined result nahi hai — optimizer assume kar sakta hai ki yeh kabhi hoga hi nahi.
  • (c) UB. Division by zero. Koi "returns 0" rule nahi hai; yeh undefined hai (aksar x86 pe trap karta hai).
  • (d) Implementation-defined. sizeof(int) compiler ki ek documented choice hai (commonly 4).
  • (e) Unspecified. g() aur h() ko evaluate karne ka order unspecified hai — compiler kisi ko bhi pehle run kar sakta hai, aur batana zaroori nahi. (Yeh UB tab hi banta hai jab do sub-expressions kisi sequence point ke bina same object pe clash karein — dekho Sequence Points and Operator Precedence.)

Recall Solution L1.2

True — UB. "hi" ek string literal hai; uspe write karna undefined hai (literals read-only memory mein reh sakte hain). Pointer read s[0] theek hota; write woh crime hai. Fix: char s[] = "hi"; ek writable array mein copy karta hai.


Level 2 — Application

Goal: predict karo ki optimizer ko kya karne ka license hai, aur defined form mein rewrite karo.

Recall Solution L2.1

(a) Signed overflow UB hai, isliye compiler assume karta hai ki x + 1000 kabhi overflow nahi karta. Us assumption ke under x + 1000 < x hamesha false hai → poora if body dead code hai aur remove ho jaata hai. Tumhara guard -O2 pe gayab ho jaata hai. (b) Defined domain mein reason karo — add karne se pehle compare karo:

if (x > INT_MAX - 1000) return INT_MAX;   // no addition overflows here
return x + 1000;

Ab koi overflow kabhi nahi hota, comparison meaningful hai, aur compiler ise delete nahi kar sakta. Dekho Compiler Optimizations and -O flags.

Recall Solution L2.2
  • (a) Defined. Ek one-past-the-end pointer form karna explicitly allowed hai. Figure s01 dekho: index 5 pe dashed slot ek legal address hai.
  • (b) UB. Yeh index 5 dereference karta hai — last valid element (0..4) se ek past. Ise read karna undefined hai.
  • (c) UB. &a[6] form karna bhi (end se do past) undefined hai — tum sirf ek past ja sakte ho.
  • (d) Defined. a + 4 last valid element hai; index 4 dereference karna theek hai.
Figure — Undefined behavior — comprehensive list, why to avoid

Level 3 — Analysis

Goal: explain karo KYU ek program jo "sahi answer print kiya" phir bhi broken hai, compiler ki reasoning trace karte hue.

Recall Solution L3.1

local ek automatic variable hai — iska lifetime usi waqt khatam hota hai jab bad() return karta hai. Return hua pointer ab dangling hai: yeh ek stack slot ko naam deta hai jise program reuse karne ke liye free hai. *p access karna ek aise object ko access karna hai jo apni lifetime ke bahar hai → UB.

Usne 42 sirf isliye print kiya kyunki kisi ne woh slot abhi tak overwrite nahi kiya tha — pure timing luck. Ek alag optimization level, ek stack-protector, ya bad() aur printf ke beech ek extra function call answer badal deta hai ya crash karta hai. Figure s02 slot ko recycle hota dikhata hai. "Works once" UB ki sabse khatarnaak symptom hai kyunki yeh bug tab tak hide karta hai jab tak production na aa jaaye. Fix: value se return karo, ya malloc karo aur caller ko free karne do — dekho malloc and free — Dynamic Memory.

Figure — Undefined behavior — comprehensive list, why to avoid
Recall Solution L3.2

Line (A) pe code p ko dereference karta hai. NULL dereference karna UB hai, isliye compiler infer karta hai: "programmer ne promise kiya ki p, NULL nahi hai — warna (A) UB hai, jo main assume kar sakta hoon kabhi nahi hota." Woh promise aage lete hue, (B) pe p == NULL false hona chahiye, toh compiler if delete kar deta hai.

Yeh UB reasoning downstream propagate ho rahi hai: symptom (B) pe missing null check) buggy line pe nahi hai. Fix: pehle dereference se pehle check karo:

if (p == NULL) return -1;
int x = *p;

Level 4 — Synthesis

Goal: multiple UB rules combine karo; ek poori function audit karo aur har defect fix karo.

Recall Solution L4.1

Teen defects hain:

  1. Out-of-bounds write. i <= 4 se i = 0..4 run hota hai; buf[4] last valid index (0..3) se past hai. Use write karna UB hai. → i < 4 use karo.
  2. Shift + signed overflow. r << k: agar k value ko INT_MAX se zyada bana de, toh sign bit mein / range se bahar left-shift karna UB hai, aur negative r ko left-shift karna bhi UB hai. Saath hi n % 32 sirf tab theek hai jab n != 0 ho (dekho #3). Shift unsigned pe karo.
  3. Division by zero + INT_MIN / -1. sum / n UB hai jab n == 0 (div-by-zero) aur tab bhi jab sum == INT_MIN && n == -1 (overflow). Dono ko guard karna zaroori hai.

Corrected:

int process(int n) {
    if (n == 0) return 0;                 // guard div-by-zero and n%0
    int buf[4];
    for (int i = 0; i < 4; i++)           // fix bounds
        buf[i] = i * i;
    int r = 0;
    for (int i = 0; i < 4; i++)
        r += buf[i];                      // r = 0+1+4+9 = 14
    unsigned shift = (unsigned)(n % 32);
    unsigned sum = (unsigned)r << shift;  // defined wraparound
    if (r == INT_MIN && n == -1) return INT_MAX;  // guard INT_MIN/-1
    return (int)sum / n;
}

Loop fix hone ke baad, buf = {0,1,4,9} aur r = 14. n = 2 ke liye: n % 32 = 2, sum = 14 << 2 = 56, return 56 / 2 = 28. Zero diagnostics confirm karne ke liye Sanitizers — ASan and UBSan ke under chalao.


Recall Solution L4.2

Unspecified vs UB: f(g(), h()) ke saath, do calls ka order unspecified hai, lekin har call ek self-contained, sequenced piece of work hai jo alag state touch karta hai — koi conflict nahi. f(i++, i++) ke saath, same object i ko bina kisi sequence point ke do baar modify kiya ja raha hai jo dono modifications ko ek doosre ke relative order nahi karta → UB (Category 3). Result "5 aur 6 ka koi order" nahi hai — yeh kuch bhi hai. Fix — haath se ordering introduce karo:

int a = i++;      // sequenced
int b = i++;      // sequenced after a
f(a, b);          // now defined: passes original i, then i+1

Level 5 — Mastery

Goal: ek saath language lawyer aur compiler ki tarah reason karo; UB-free interfaces design karo.

Recall Solution L5.1

Hume overflow pehle detect karna hoga addition perform karne se, kyunki pehle perform karna already UB hoga (aur optimizer hamaara test delete kar sakta tha — L2.1 yaad karo).

bool safe_add(int a, int b, int *out) {
    if (b > 0 && a > INT_MAX - b) return false;  // a+b would exceed INT_MAX
    if (b < 0 && a < INT_MIN - b) return false;  // a+b would go below INT_MIN
    *out = a + b;                                 // now provably in range
    return true;
}

Yeh forms defined kyun hain: b > 0 ke saath INT_MAX - b underflow nahi kar sakta (max se positive subtract karna range mein rehta hai); b < 0 ke saath INT_MIN - b overflow nahi kar sakta (min se negative subtract karna range mein rehta hai). Har guard completely safe domain ke andar compute hota hai, isliye compiler ise UB-dependent samajh ke dismiss nahi kar sakta. Checks: safe_add(INT_MAX, 1, &o)false. safe_add(2000000000, 200000000, &o)false. safe_add(100, 200, &o)true, o == 300. safe_add(INT_MIN, -1, &o)false.

Recall Solution L5.2

Yeh strict aliasing rule violate karta hai — dekho Strict Aliasing Rule. Rule kehta hai ki float type ke object ko ek int lvalue ke through nahi read kiya ja sakta (int aur float incompatible types hain). Compiler assume karta hai ki unrelated types ke pointers kabhi same storage point nahi karte, isliye woh accesses reorder ya elide kar sakta hai. Equal sizeof irrelevant hai — yeh ek type rule hai, size rule nahi. Fix — memcpy use karo (ya C11 mein ek union):

int bits;
memcpy(&bits, &myfloat, sizeof bits);   // byte copy, no aliasing violation

memcpy bytes ko unsigned char ke roop mein access karta hai, jo kisi bhi cheez ke saath alias karne ki izaazat hai, isliye reinterpretation well-defined hai.


Active Recall

Recall Quick self-check

Kya UINT_MAX + 1 UB hai? ::: Nahi — unsigned overflow defined hai, yeh 0 pe wrap karta hai. Ek dereference ke baad NULL-check kyun delete ho sakti hai? ::: Dereference NULL pe UB hai, isliye compiler assume karta hai pointer non-null hai aur redundant test remove kar deta hai. Kya tum int a[n] ke liye &a[n] form kar sakte ho? ::: Haan, one-past pointer form karna defined hai; ise dereference karna (a[n]) UB hai. a+b mein signed overflow detect karne ka safe tarika? ::: Add karne se pehle compare karo: b>0 && a>INT_MAX-b, ya b<0 && a<INT_MIN-b. Float ke bits ko int ke roop mein reinterpret karne ka sahi tarika? ::: memcpy (ya union) — pointer cast strict aliasing violate karta hai.