pointers-memory Lesson 5 18 min read

How Do I Review Pointers and Prepare for Advanced Topics?

Practice problems, common mistakes, and solidifying your mental model before dynamic memory

Reading: C Text: Ch. 6 (Pointers review), Ch. 7 §5 (Array-pointer equivalence)

After this lesson, you will be able to:

  • Trace multi-step pointer code involving reassignment and dereferencing
  • Identify the five most common pointer mistakes
  • Explain why returning a pointer to a local variable causes undefined behavior
  • Use both array notation (p[i]) and pointer notation (*(p + i)) interchangeably

Before You Go Deeper, Check Your Foundation

Dynamic memory allocation (next lesson) builds on everything you’ve learned about pointers. If you’re shaky on &, *, or pointer arithmetic, dynamic memory will feel impossible. If you’re solid, it’ll click.

This lesson is a checkpoint — practice problems, common mistakes, and a chance to make sure your mental model is right before we add calloc and free to the mix.


Review and Practice

Quick Reference

Expression Meaning
int *p Declare p as pointer to int
p = &x Set p to address of x
*p Follow pointer — access value at address
*p = 42 Write 42 to where p points
p + n Address n elements after p
p++ Move pointer to next element
arr[i] Same as *(arr + i)
int **pp Pointer to pointer to int
**pp Follow two levels of indirection

Trace This Code

Before reading the answer, draw boxes and arrows:

int a = 1, b = 2, c = 3;
int *p = &a;
int *q = &b;

*p = *q;          // Line 1
p = &c;           // Line 2
*p = *q + *&a;    // Line 3
Click to see the trace

After Line 1: *p = *q → write the value at q’s target (2) to p’s target (a). Now a = 2, b = 2, c = 3.

After Line 2: p = &c → p now points to c. a = 2, b = 2, c = 3.

After Line 3: *q is 2 (b). *&a is 2 (address of a, then dereference — just a). *p = 2 + 2 = 4 → c = 4. Final: a = 2, b = 2, c = 4.

The Five Most Common Pointer Mistakes

1. Forgetting & when calling a function:

scanf("%d", age);      // WRONG: passes value
scanf("%d", &age);     // RIGHT: passes address

2. Forgetting * when writing through a pointer:

void set_zero(int *p)
{
    p = 0;             // WRONG: changes local copy of pointer
    *p = 0;            // RIGHT: writes to the address
}

3. Dereferencing NULL:

int *p = NULL;
*p = 42;               // CRASH: segmentation fault

4. Using an uninitialized pointer:

int *p;                // Garbage address!
*p = 42;               // Undefined behavior

5. Returning a pointer to a local variable:

int *bad_function(void)
{
    int x = 42;
    return &x;         // WRONG: x is destroyed when function returns
}

Common Pitfall: Never return the address of a local variable. When the function returns, the local variable’s stack frame is reclaimed. The pointer now points to freed memory — using it is undefined behavior. (This is exactly what dynamic memory allocation solves in the next lesson.)

Check Your Understanding
What's the bug in this code?

int *make_value(void) {
    int val = 42;
    return &val;
}
A Missing free() call — memory leak
B Returns a dangling pointer — val is destroyed when the function returns
C Can't use & on a local variable
D Should return val instead of &val
Answer: B. val lives on the stack in make_value's stack frame. When the function returns, that frame is destroyed. The returned pointer now points to reclaimed memory — using it is undefined behavior. The fix: allocate on the heap with calloc (Lesson 3.7), or return the value directly instead of a pointer. Option A is wrong because no heap allocation happened. Option D would work but changes the function's purpose.

Arrays and Pointers: A Final Check

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

printf("%d\n", *p);           // 10
printf("%d\n", *(p + 2));     // 30
printf("%d\n", p[3]);         // 40 (yes, this works on pointers too!)
printf("%d\n", arr[4]);       // 50
printf("%d\n", *(arr + 4));   // 50 (same thing)

p[3] works because p[3] is defined as *(p + 3). Array notation works on any pointer, not just arrays.

Self-Assessment

Sanity Check: You’re ready for dynamic memory if you can answer all of these:

  1. What’s the difference between p and *p?
  2. Why does swap(int a, int b) fail but swap(int *a, int *b) work?
  3. What does *(arr + i) evaluate to?
  4. Why can’t you return a pointer to a local variable?
  5. What does int **pp mean and when would you use it?
Check Your Understanding
Trace this code. What are the final values of a, b, and c?

int a = 1, b = 2, c = 3;
int *p = &a;
int *q = &b;
*p = *q;
p = &c;
*p = *q + *&a;
A a = 1, b = 2, c = 3
B a = 2, b = 2, c = 3
C a = 2, b = 2, c = 5
D a = 2, b = 2, c = 4
Answer: D. Step by step: *p = *q writes b's value (2) into a, so a = 2. p = &c redirects p to c. *p = *q + *&a computes *q (b = 2) + *&a (a = 2) = 4, writes to c. Final: a = 2, b = 2, c = 4. Option C gets the arithmetic wrong (2 + 2 = 4, not 5). Options A and B miss the pointer reassignment.
Quick Check: What's the output of printf("%d", *&*&x) if x = 42?
  1. &x gets x’s address, *&x dereferences it back to x (42), &*&x gets the address again, *&*&x dereferences again. Each & and * cancel out. It’s just x.
Quick Check: Can you use p[i] on a pointer that isn't an array?

Technically yes — p[i] is defined as *(p + i) for any pointer. But it’s only safe if p points to a block of memory that’s at least i + 1 elements long. Using p[3] when p points to a single int is undefined behavior.

Quick Check: Given int x = 7; int *p = &x; int **pp = &p;, what does **pp = 99 do?

It sets x to 99. pp points to p, and p points to x. The first * follows pp to get p, the second * follows p to get x. So **pp = 99 writes 99 into x. After this line, x == 99, *p == 99, and **pp == 99 — they’re all looking at the same memory.


Foundation Solid — Time for the Heap

If you can trace pointer code with diagrams and avoid the five common mistakes, you’re ready. The next five lessons are the core of the memory management arc:

  • Lesson 3.6: How memory is organized (stack, heap, text, data segments)
  • Lesson 3.7: Dynamic allocation with calloc and free
  • Lesson 3.8: Growing arrays with realloc
  • Lesson 3.9: Finding memory bugs with Valgrind
  • Lesson 3.10: Putting it all together in a real program

This is the sequence that separates C from Java. In Java, the garbage collector handles memory. In C, you handle it — and the tools you’ve just reviewed are how.

Big Picture: This checkpoint marks the divide between “C syntax” and “C programming.” Everything before this was learning the notation. Everything after this — heap allocation, realloc, Valgrind — is learning to manage the machine’s resources directly. If you can trace pointer code on paper, you’re ready for what comes next.