← Back to Guide

📝 Practice Problems

Exam-style problems with solutions

17
Problems
5
Exam Traps
4
Topics
💻

C Fundamentals

Problem 1: Pointer Arithmetic

Given this code:

int arr[5] = {10, 20, 30, 40, 50}; int *ptr = arr + 2;

What is the value of:

  • a) *ptr
  • b) *(ptr + 1)
  • c) *(ptr - 1)
  • d) ptr[1]

Solution

  • a) *ptr: arr + 2 points to arr[2], so *ptr = 30
  • b) *(ptr + 1): Points to arr[3], so *(ptr + 1) = 40
  • c) *(ptr - 1): Points to arr[1], so *(ptr - 1) = 20
  • d) ptr[1]: Same as *(ptr + 1) = 40

Key: ptr + n moves n * sizeof(int) bytes. Array indexing ptr[n] is syntactic sugar for *(ptr + n).

Problem 2: Stack Frame Layout

Draw the stack layout after this code executes:

void foo(int a, int b) { int x = 10; int y = 20; // Stack layout here? } int main() { foo(5, 15); return 0; }

Solution

Higher addresses +------------------+ | main's stack | +------------------+ | Return address | ← Pushed by call foo +------------------+ | saved rbp | ← rbp after push rbp +------------------+ | param a (slot 0) | ← [rbp - 8] (value: 5, from rdi) +------------------+ | param b (slot 1) | ← [rbp - 16] (value: 15, from rsi) +------------------+ | local x (slot 2) | ← [rbp - 24] (value: 10) +------------------+ | local y (slot 3) | ← [rbp - 32] (value: 20) +------------------+ ← rsp Lower addresses

Key: Parameters saved from registers (rdi=a, rsi=b) to stack. Variables accessed via [rbp - (slot+1)*8].

Problem 3: Struct Memory Alignment

What is sizeof(struct Point) and why?

struct Point { char c; // 1 byte int x; // 4 bytes char d; // 1 byte int y; // 4 bytes };

Solution: 16 bytes (not 10!)

Offset Field Size Padding 0 c 1 3 bytes padding (align next int) 4 x 4 no padding 8 d 1 3 bytes padding (align next int) 12 y 4 no padding Total: 16 bytes

Why? int requires 4-byte alignment. After each char, 3 padding bytes are added.

Better layout (12 bytes):

struct Point { int x; // 4 bytes at offset 0 int y; // 4 bytes at offset 4 char c; // 1 byte at offset 8 char d; // 1 byte at offset 9 // 2 bytes padding to align struct };

Problem: Fix the Swap Function

This code doesn't swap the values. Why not, and how do you fix it?

void swap(int a, int b) { int temp = a; a = b; b = temp; } int main() { int x = 5, y = 10; swap(x, y); printf("%d %d\n", x, y); // Prints: 5 10 (not swapped!) }

Problem: Pass-by-value

swap receives copies of x and y, not the originals. Changes to a and b don't affect x and y.

Fix: Use pointers

void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; swap(&x, &y); // Pass addresses printf("%d %d\n", x, y); // Prints: 10 5 ✓ }

Problem: Find the Memory Leak

This code has a memory leak. Find and fix it:

void process_data(const char *filename) { FILE *f = fopen(filename, "r"); if (f == NULL) return; char *buffer = malloc(1000); if (buffer == NULL) { fclose(f); return; } fread(buffer, 1, 1000, f); if (some_error_condition()) { return; // ← Is there a leak here? } free(buffer); fclose(f); }

Yes! Memory leak on early return

If some_error_condition() is true, we return without freeing buffer or closing f!

Fix: Free resources before all returns

if (some_error_condition()) { free(buffer); // ✓ Free before return fclose(f); // ✓ Close before return return; }

Better pattern: Use goto cleanup for centralized resource cleanup.

Problem: Returning Local Array

Is this code safe? Why or why not?

char *create_greeting() { char msg[] = "Hello"; return msg; } int main() { char *greeting = create_greeting(); printf("%s\n", greeting); }

NO! Undefined behavior

msg is a local array on the stack. When the function returns, that stack space is invalid. The returned pointer is dangling.

Fix #1: Return string literal

char *create_greeting() { return "Hello"; // String literal lives forever }

Fix #2: Allocate on heap

char *create_greeting() { char *msg = malloc(6); strcpy(msg, "Hello"); return msg; // Caller must free! }
⚙️

x86-64 Assembly

Problem 4: Register Operations

What is the value in rax after this sequence?

mov rax, 10 mov rcx, 3 imul rcx add rax, 5

Solution: rax = 35

1. mov rax, 10 → rax = 10 2. mov rcx, 3 → rcx = 3 3. imul rcx → rdx:rax = rax * rcx = 10 * 3 = 30 (rax = 30) 4. add rax, 5 → rax = 30 + 5 = 35

Note: imul with one operand multiplies rax × operand and stores result in rdx:rax.

Problem 5: Conditional Jumps

What does this code return?

mov rax, 5 mov rcx, 10 cmp rax, rcx jl .less mov rax, 0 jmp .end .less: mov rax, 1 .end: ret

Solution: Returns 1

1. rax = 5, rcx = 10 2. cmp rax, rcx → 5 - 10 = -5 (negative) 3. jl .less → Jump if less (5 < 10 is TRUE) 4. mov rax, 1 → rax=1 5. ret → Return 1

Pattern: This is how if (a < b) return 1; else return 0; compiles.

Problem 6: Stack Operations

What's on the stack after this sequence? (Assume rsp starts at 0x1000)

push 10 push 20 pop rax push 30

Solution

After push 10: Stack = [10], rsp = 0x0FF8 After push 20: Stack = [10, 20], rsp = 0x0FF0 After pop rax: Stack = [10], rax = 20, rsp = 0x0FF8 After push 30: Stack = [10, 30], rsp = 0x0FF0 Final: Stack = [30 (top), 10], rax = 20

Key: Stack grows downward (push decrements rsp). Pop reads value then increments rsp.

Problem: Register Size Trap

What is the value of rax after this sequence?

mov rax, 0xFFFFFFFFFFFFFFFF mov eax, 0x12345678 ; What is rax now?

Answer: 0x0000000012345678

Before: rax = 0xFFFFFFFFFFFFFFFF After: rax = 0x0000000012345678 └──────┘└──────┘ upper 32 lower 32 (zeroed) (eax)

Key insight: Writing to a 32-bit register (eax) zeros the upper 32 bits of the 64-bit register (rax)!

Problem: Memory Operand Rules

Which of these x86-64 instructions are VALID?

A. mov rax, [rbx] B. mov [rax], [rbx] C. mov [rax], 42 D. add rax, [rbx] E. add [rax], [rbx]

Valid: A, C, D

Invalid: B, E

Rule: At most ONE operand can be a memory reference!

B invalid: mov [rax], [rbx] ← TWO memory refs E invalid: add [rax], [rbx] ← TWO memory refs

Fix for B:

mov rcx, [rbx] ; Load to temp register mov [rax], rcx ; Store from temp

Problem: Two's Complement Calculation

What is the signed decimal value of this 8-bit binary: 0b11111101?

Answer: -3

Bit: 7 6 5 4 3 2 1 0 Value: 1 1 1 1 1 1 0 1 = -1×128 + 1×64 + 1×32 + 1×16 + 1×8 + 1×4 + 0×2 + 1×1 = -128 + 64 + 32 + 16 + 8 + 4 + 0 + 1 = -128 + 125 = -3

Quick trick: MSB (bit 7) contributes -128. Add remaining 1-bits as positive.

🌳

Expression Parsing & AST

Problem 7: Operator Precedence

Draw the AST for: 1 + 2 * 3 - 4 / 2

Solution

- / \ + / / \ / \ 1 * 4 2 / \ 2 3

Evaluation (post-order):

  1. 2 * 3 = 6
  2. 1 + 6 = 7
  3. 4 / 2 = 2
  4. 7 - 2 = 5

Result: 5

Problem 8: Parentheses Override

Draw the AST for: (1 + 2) * (3 - 4)

Solution

* / \ + - / \ / \ 1 2 3 4

Evaluation:

  1. 1 + 2 = 3
  2. 3 - 4 = -1
  3. 3 * (-1) = -3

Result: -3

Problem 9: Left Associativity

Draw the AST for: 10 - 5 - 2

Solution (Left-Associative)

CORRECT: WRONG (right-assoc): - - / \ / \ - 2 10 - / \ / \ 10 5 5 2

Correct evaluation: (10 - 5) - 2 = 5 - 2 = 3

Wrong evaluation: 10 - (5 - 2) = 10 - 3 = 7

Key: Subtraction and division are left-associative!

📚

Stack Machine Execution

Problem 10: Stack Machine Trace

Trace the stack for these instructions:

PUSH 5 PUSH 3 ADD PUSH 2 MUL RETURN

Solution

PUSH 5: Stack = [5] PUSH 3: Stack = [5, 3] ADD: Pop 3, pop 5, push 5+3=8 → Stack = [8] PUSH 2: Stack = [8, 2] MUL: Pop 2, pop 8, push 8*2=16 → Stack = [16] RETURN: Pop 16, return 16 → Stack = []

Original expression: (5 + 3) * 2 = 16

Problem 11: Stack Machine with Variables

Trace execution (a=slot 0, b=slot 1):

PUSH 10 STORE a (slot 0) PUSH 5 STORE b (slot 1) LOAD a LOAD b MUL RETURN

Solution

PUSH 10: Stack = [10] STORE a: Pop 10, a = 10 → Stack = [] PUSH 5: Stack = [5] STORE b: Pop 5, b = 5 → Stack = [] LOAD a: Push a=10 → Stack = [10] LOAD b: Push b=5 → Stack = [10, 5] MUL: Pop 5, pop 10, push 50 → Stack = [50] RETURN: Pop 50, return 50

Original code:

let a: int = 10 let b: int = 5 return a * b
⚠️

Common Exam Traps

Trap 1: Off-by-One Errors

int arr[5]; for (int i = 0; i <= 5; i++) { // BUG: i <=5 arr[i]=i; // When i=5, out of bounds! }
✅ How to Avoid

Array of size N has indices 0 to N-1. Use i < N not i <= N.

Trap 2: Operator Precedence

What's 2 + 3 * 4?

  • ❌ Wrong: (2 + 3) * 4 = 20
  • ✅ Correct: 2 + (3 * 4) = 14
✅ How to Avoid

Memorize: * / % before + -. When in doubt, use parentheses.

Trap 3: Signed vs Unsigned Jumps

mov rax, -5 cmp rax, 0 ja .positive ; WRONG! Uses unsigned comparison
✅ How to Avoid

Signed: jl, jg, jle, jge | Unsigned: jb, ja, jbe, jae

Trap 4: Forgetting cqo Before idiv

mov rax, 23 mov rcx, 4 idiv rcx ; BUG: rdx contains garbage!
✅ How to Avoid

Always use cqo before idiv to sign-extend rax into rdx:rax.

Trap 5: Stack Offset Calculation

Variable at slot 0:

  • ❌ Wrong: [rbp - 0] = [rbp] (that's saved rbp!)
  • ✅ Correct: [rbp - 8]
✅ Formula

Variable at slot N → [rbp - (N+1) * 8]