Coding interleaved practice
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 1 — Subtopic 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(binary1010).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 2 — Subtopic 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 3 — Subtopic 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 4 — Subtopic 5.1.9/5.1.10 (pointer arithmetic & array decay).
(a) *(p + 3) = a[3] = 40.
(b) *(a + 1) = a[1] = 20; + 1 → 21.
(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 5 — Subtopic 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 6 — Subtopic 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 7 — Subtopic 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 8 — Subtopic 5.1.6 (control flow: for/continue/break/switch).
Trace: i=0 → prints 0; i=1 → continue (skip); i=2 → prints 2; i=3 → break (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 9 — Subtopic 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"
}
]