How Do I Review Pointers and Prepare for Advanced Topics?
Practice problems, common mistakes, and solidifying your mental model before dynamic memory
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.)
int *make_value(void) { int val = 42; return &val;}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:
- What’s the difference between
pand*p?- Why does
swap(int a, int b)fail butswap(int *a, int *b)work?- What does
*(arr + i)evaluate to?- Why can’t you return a pointer to a local variable?
- What does
int **ppmean and when would you use it?
a, b, and c?int a = 1, b = 2, c = 3;int *p = &a;int *q = &b;*p = *q;p = &c;*p = *q + *&a;*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?
&xgets x’s address,*&xdereferences it back to x (42),&*&xgets the address again,*&*&xdereferences again. Each&and*cancel out. It’s justx.
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
callocandfree - 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.