Lesson 97 min read

C Pointers

A sticky note with an address on it β€” it tells you WHERE something is, not WHAT it is

The Sticky Note Analogy

Imagine you have a box in a warehouse at shelf B7. A pointer is a sticky note that says "B7" β€” it doesn't contain the item, it just tells you where to find it.

In C, every variable lives at a specific memory address. A pointer is a variable that stores that address. Instead of holding a number or a character, it holds the location of another variable.

Two Key Operators

  • & (address-of) β€” "What's the address of this box?" Give me the sticky note.
  • * (dereference) β€” "Go to this address and get what's inside." Follow the sticky note to the box.

These two operators are inverses. *(&x) gets the address of x, then immediately follows it back β€” so it's just x again.

Basic Pointer Usage

#include <stdio.h>
int main() {
int age = 25;
int *ptr = &age; // ptr holds the address of age
printf("Value of age: %d\n", age);
printf("Address of age: %p\n", (void*)&age);
printf("Value of ptr: %p\n", (void*)ptr);
printf("Dereferencing ptr: %d\n", *ptr);
// Modify age through the pointer
*ptr = 30;
printf("\nAfter *ptr = 30:\n");
printf("age is now: %d\n", age);
return 0;
}
Output
Value of age:     25
Address of age:   0x7ffd5e8a3b4c
Value of ptr:     0x7ffd5e8a3b4c
Dereferencing ptr: 25

After *ptr = 30:
age is now: 30

Pointer Types Matter

A pointer has a type β€” int*, char*, float*. The type tells the compiler how many bytes to read when you dereference. An int* reads 4 bytes, a char* reads 1 byte, a double* reads 8 bytes.

You can think of it this way: the address tells you which locker to go to, and the type tells you how many lockers to open to get the full value.

NULL β€” The Pointer to Nowhere

NULL is a special value meaning "this pointer doesn't point to anything." It's used to signal that a pointer is intentionally empty. You should always initialize pointers to NULL if you don't have an address for them yet.

Printing Addresses of Different Types

#include <stdio.h>
int main() {
int i = 42;
char c = 'A';
double d = 3.14;
int *ip = &i;
char *cp = &c;
double *dp = &d;
printf("int: value=%d, size=%lu bytes\n", *ip, sizeof(int));
printf("char: value=%c, size=%lu byte\n", *cp, sizeof(char));
printf("double: value=%.2f, size=%lu bytes\n", *dp, sizeof(double));
// NULL pointer
int *nothing = NULL;
if (nothing == NULL) {
printf("\n'nothing' points to NULL β€” safe to check!\n");
}
return 0;
}
Output
int:    value=42,  size=4 bytes
char:   value=A,  size=1 byte
double: value=3.14, size=8 bytes

'nothing' points to NULL β€” safe to check!

Pointers Unlock Pass-by-Reference

Remember how C is pass-by-value? Functions get a copy of arguments, so they can't change the originals. But if you pass a pointer to the variable, the function gets a copy of the address β€” and it can use that address to reach into the caller's memory and modify the original.

The classic example is a swap function. Without pointers, it's impossible. With pointers, it's elegant.

Swap β€” Without and With Pointers

#include <stdio.h>
// BROKEN: swaps copies, not originals
void brokenSwap(int a, int b) {
int temp = a;
a = b;
b = temp;
// a and b are local copies β€” changes die here
}
// WORKS: uses pointers to reach the originals
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("Before brokenSwap: x=%d, y=%d\n", x, y);
brokenSwap(x, y);
printf("After brokenSwap: x=%d, y=%d\n\n", x, y);
printf("Before swap: x=%d, y=%d\n", x, y);
swap(&x, &y); // pass addresses!
printf("After swap: x=%d, y=%d\n", x, y);
return 0;
}
Output
Before brokenSwap: x=10, y=20
After brokenSwap:  x=10, y=20

Before swap: x=10, y=20
After swap:  x=20, y=10
Note: Dereferencing NULL or uninitialized pointers causes a segfault (segmentation fault). The program crashes immediately. Always initialize pointers β€” either to a valid address or to NULL β€” and check for NULL before dereferencing. An uninitialized pointer holds a random garbage address, which is even worse than NULL because it might silently corrupt data instead of crashing.

Common Pointer Mistakes

Pointers are powerful but unforgiving. Here are the classic traps:

  • Forgetting & β€” passing the value instead of the address to a function expecting a pointer
  • Dangling pointers β€” pointing to memory that's been freed or a local variable that went out of scope
  • Uninitialized pointers β€” using a pointer before assigning it an address
  • Type mismatch β€” assigning an int* to a char* without understanding the consequences

The good news: these mistakes get easier to spot with practice. The compiler warnings are your friend β€” never ignore them.

A Practical Example: Function Returning Multiple Values

#include <stdio.h>
// C functions can only return ONE value.
// Use pointers to "return" multiple values.
void minMax(int arr[], int size, int *min, int *max) {
*min = arr[0];
*max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main() {
int data[] = {34, 12, 78, 5, 91, 23};
int lo, hi;
minMax(data, 6, &lo, &hi);
printf("Min: %d\n", lo);
printf("Max: %d\n", hi);
return 0;
}
Output
Min: 5
Max: 91
Challenge

Quick check

What does the & operator do?
← FunctionsPointer Arithmetic & Arrays β†’