Interleaved — Phase 5

Coding interleaved practice

printable — key stays hidden on paper

Instructions: Solve each problem below. These problems deliberately mix subtopics, so read carefully and decide which C concept applies before answering. Assume a typical 64-bit Linux/x86-64 system (LP64: int=4 bytes, long=8 bytes, pointers=8 bytes) unless stated otherwise. Show reasoning where asked. Total: 50 marks.


Problem 1. (5 marks) Given the declaration below, state the value printed and explain why:

int x = 5, y = 2;
printf("%d\n", x & y | x << 1);

Evaluate using operator precedence — do not assume left-to-right.


Problem 2. (6 marks) Consider:

struct Packet {
    unsigned int type   : 3;
    unsigned int flag   : 1;
    unsigned int length : 12;
};

(a) What is the minimum number of bits requested by the three fields, and what is a likely sizeof(struct Packet) on a 32-bit-int machine? (b) Explain why you cannot take the address of p.flag with &.


Problem 3. (5 marks) The C build pipeline transforms main.c into an executable. Name the four stages in order and state which stage: (a) expands #include and #define, (b) resolves the reference to an external printf symbol.


Problem 4. (6 marks) Given:

int a[5] = {10, 20, 30, 40, 50};
int *p = a;

Compute the value of each expression and briefly justify: (a) *(p + 3) (b) *(a + 1) + 1 (c) p[2] (d) The value of (a == &a[0]) (0 or 1?).


Problem 5. (6 marks) The following function has a serious memory bug. Identify the specific error category, explain when it manifests, and name the Valgrind check that would catch it:

char *make_greeting(const char *name) {
    char buf[16];
    strcpy(buf, "Hello, ");
    strcat(buf, name);
    return buf;
}

Problem 6. (5 marks) Declare a function pointer fp that points to a function taking two int arguments and returning int. Then, assuming a function int add(int,int) exists, write the line that assigns add to fp and the line that calls it with (3,4).


Problem 7. (5 marks) For a union defined as:

union U {
    int   i;
    float f;
    char  c[4];
};

(a) What is sizeof(union U)? (b) If you write to u.i then read u.f, is the result well-defined? Explain in one sentence what physically happens.


Problem 8. (6 marks) Trace this control flow and give the exact printed output:

for (int i = 0; i < 4; i++) {
    if (i == 1) continue;
    if (i == 3) break;
    printf("%d ", i);
}

Then rewrite the loop body's effect using a switch on i that produces the identical output for i in 0..3.


Problem 9. (6 marks) You must allocate an array of 100 ints, zero-initialized, then grow it to 200 elements. (a) Which allocation function zero-initializes? (b) Write the realloc call that grows the buffer safely (i.e., without leaking the original block if realloc fails). (c) In which memory segment does this allocation live?


Answer keyMark scheme & solutions

Problem 1Subtopic 5.1.5 (precedence) + 5.1.4 (bitwise operators). Precedence: << (shift) binds tighter than &, which binds tighter than |. So the expression is (x & y) | (x << 1).

  • x << 1 = 5 << 1 = 10 (binary 1010).
  • x & y = 5 & 2 = 0101 & 0010 = 0.
  • 0 | 10 = 10. Answer: 10. Why this subtopic: Students who default to left-to-right get the wrong answer; the problem forces recall of the precedence table (shift > bitwise-AND > bitwise-OR).

Problem 2Subtopic 5.1.23 (bit fields) + 5.1.22 (structs). (a) Bits requested: 3 + 1 + 12 = 16 bits. They fit within one 32-bit unsigned int storage unit, so sizeof(struct Packet) is typically 4 bytes. (b) You cannot apply & to a bit field because bit fields do not occupy an addressable whole-byte boundary — they may share a byte with other fields, so no valid pointer/address exists for a sub-byte field. Why: Tests understanding that bit fields trade addressability for packing.


Problem 3Subtopic 5.1.1 (compilation pipeline). Stages in order: Preprocessor → Compiler → Assembler → Linker. (a) The preprocessor expands #include and #define. (b) The linker resolves the external printf symbol (from libc). Why: Distinguishes symbol resolution (link time) from macro expansion (preprocess time) — commonly confused.


Problem 4Subtopic 5.1.9/5.1.10 (pointer arithmetic & array decay). (a) *(p + 3) = a[3] = 40. (b) *(a + 1) = a[1] = 20; + 121. (c) p[2] = a[2] = 30. (d) a decays to &a[0], so a == &a[0] is 1 (true). Why: Forces the equivalence *(a+i) == a[i] and the array-name-decays-to-pointer rule.


Problem 5Subtopic 5.1.18 (memory errors) + 5.1.15/5.1.16 (stack) + 5.1.20 (string dangers) + 5.1.19 (Valgrind). The function returns the address of a local (stack) array buf. After make_greeting returns, its stack frame is destroyed, so the returned pointer is dangling — using it is undefined behavior (a use-after-return / return-of-stack-address error). Additionally, strcat can buffer-overflow buf[16] if name is long — a compounding bug. It manifests when the caller dereferences the returned pointer. Valgrind's Memcheck flags invalid reads/writes and use of uninitialized/out-of-scope stack memory (though stack-return is best caught by ASan; Memcheck catches the heap/buffer variants and invalid accesses). Fix: allocate with malloc and use snprintf. Why: Interleaves lifetime rules with string-safety; the trap is thinking strcpy/strcat is the only bug.


Problem 6Subtopic 5.1.13 (function pointers).

int (*fp)(int, int);   // declaration
fp = add;              // (or fp = &add;)
int r = fp(3, 4);      // call  (or (*fp)(3,4))

Why: Tests correct parenthesization (*fp) — without it, int *fp(int,int) is a function returning int*.


Problem 7Subtopic 5.1.24 (unions). (a) sizeof(union U) = 4 — the size of the largest member (int, float, and char[4] are all 4 bytes). (b) Reading u.f after writing u.i is type-punning; the same 4 bytes are reinterpreted as an IEEE-754 float. Physically no conversion happens — the identical bit pattern is read under a different type. (Allowed in C as implementation-defined via union punning, but the value is not the arithmetic conversion of the int.) Why: Distinguishes overlapping storage (union) from independent members (struct), and reinterpretation vs. conversion.


Problem 8Subtopic 5.1.6 (control flow: for/continue/break/switch). Trace: i=0 → prints 0; i=1continue (skip); i=2 → prints 2; i=3break (exit). Output: 0 2 Switch equivalent:

for (int i = 0; i < 4; i++) {
    switch (i) {
        case 0: printf("%d ", 0); break;
        case 2: printf("%d ", 2); break;
        default: break;   // i==1 skipped, i==3 no print
    }
}

Why: continue skips to next iteration; break exits the loop — students must not confuse loop-break with switch-break.


Problem 9Subtopic 5.1.14 (dynamic memory) + 5.1.15 (heap) + 5.1.18 (leak avoidance). (a) calloc zero-initializes: int *arr = calloc(100, sizeof(int)); (b) Safe grow:

int *tmp = realloc(arr, 200 * sizeof(int));
if (tmp == NULL) {
    /* handle error; arr still valid, not leaked */
} else {
    arr = tmp;
}

(c) The allocation lives in the heap segment. Why: The realloc-into-temp idiom prevents the classic leak of overwriting arr with NULL on failure.


[
  {
    "claim": "P1: (x&y)|(x<<1) with x=5,y=2 equals 10",
    "code": "x=5; y=2; result = ((x & y) | (x << 1)) == 10"
  },
  {
    "claim": "P4: *(a+1)+1 = 21 and p[2] = 30 for a={10,20,30,40,50}",
    "code": "a=[10,20,30,40,50]; result = (a[1]+1 == 21) and (a[2] == 30)"
  },
  {
    "claim": "P7: sizeof union of int,float,char[4] = 4 (max member size)",
    "code": "sizes=[4,4,4]; result = max(sizes) == 4"
  }
]