Learn C pointers through memory visualization. Addresses and dereferencing explained with diagrams.
π What to learn on this page
β Must-know essentials
&x gets address, *p dereferences
Declare: int *p = &x;
NULL is an explicit "points to nothing" marker
β Read if you have time
Pointer arithmetic with arrays
Uninitialized vs dangling pointer
Pass a pointer to modify the caller's variable
πͺ Not getting it first time is normal
Pointers are the biggest wall in C. Almost nobody gets them in one pass.
How to retry
Review arrays β rebuild the "contiguous memory" intuition
Re-read this page's memory diagram (house-and-address analogy) several times
Start from the concrete swap function use case
Draw the memory and addresses on paper by hand
It's fine not to grasp it today. Move on, come back in a few days.
π‘ Tip: Most confusion is about & (take address) vs * (dereference). In a declaration, * marks "pointer type"; in an expression, *p means "what p points to." Nail that distinction.
What is a pointer?
A variable is a box in memory. Each box has an address. A pointer is a variable that stores an address.
Memory is a "numbered, contiguous warehouse"
A computer's memory (RAM) is like a long row of shelves, where each byte (8 bits) has a sequential number (address). When you declare a variable, the OS allocates a free slot and assigns it an address.
πΎ Memory (RAM) β each cell is 1 byte
int n = 10; (4 bytes)
int m = 20; (4 bytes)
char c = 'A'; (1 byte)
Unused
Key point:Start address of n = 0x1000 /
Start address of m = 0x1004 /
Address of c = 0x1008
Since int uses 4 bytes, the next variable is placed 4 addresses later. Memory is allocated contiguously, sized according to the type.
Note: this diagram assumes a typical 32/64-bit environment where int is 4 bytes. Type sizes are implementation-defined; check with sizeof(int) on your system. Real addresses also vary by OS and run, and variables aren't always laid out this neatly.
House and address analogy: A variable is a "house", the address is its "street number". A pointer is a "memo" that records that address. Knowing the address lets you read or rewrite what's inside the house.
Get an address with the & operator: Writing &n gives you the address of variable n (e.g., 0x1000). You can even print the address with printf("%p\n", &n);.
Regular variable vs pointer
Regular variable
int n = 10;
Stores the value (10) directly in the box
Pointer variable
int *p = &n;
Stores the address of n in the box
The declaration form is type *name;. The type is the type that the pointer points to.int *p means "pointer p that points to an int variable".
& operator and * operator
Two operators are essential when working with pointers.
& (address-of operator)
&n β address of n
Gets the address of a variable. Same & used in scanf!
* (dereference operator)
*p β value at the address p points to
Accesses the value the pointer points to. Can be used to both read and write.
int n = 10;
int *p = &n; // assign address of n to p
printf("%d", *p); // β 10 (value p points to)
*p = 99; // rewrites n!
printf("%d", n); // β 99
Address and value β Visualizer
Use the buttons to see the relationship between variable n and pointer p.
Address 0x1000
int n
β
β
Address 0x2000
int *p
β
Press the buttons in order...
Step through memory line by line
See how the pointer moves inside memory, one line at a time. Pointer p will switch between pointing to n and pointing to m.
Program
int n = 10;
int m = 20;
int *p;
p = &n;
*p = 99;
p = &m;
*p = 77;
πΎ Memory (RAM)
0x1000
int nβ
0x1004
int mβ
0x2000
int *pβ
Press "Execute next line"...
What to observe:
p = &n; and p = &m; switch where the arrow (target of p) points
*p = value; rewrites the value at the other end of the arrow
p itself contains an address (0x1000 or 0x1004); you need * to reach the value
The truth about & in scanf
Up to now you've been writing scanf("%d", &a); in scanf. That "&" is the operator that passes an address.
Why is & needed?
The scanf function needs to know where to write the value. That's why you pass the address of the variable.
β Internally, scanf uses a pointer to write the input value at that address.
β οΈ Common mistake: If you forget & and write scanf("%d", a);, scanf treats a's (indeterminate) contents as an address, writing to an unexpected location, which can crash the program.
swap function β a classic pointer example
A swap function that exchanges two variables is a classic use case for pointers.
Pass-by-value cannot swap! void swap(int a, int b){ ... } does not change the caller's variables. (Only local copies inside the function are swapped.)
// receive pointers and swap the values they point to
void swap(int *x, int *y){
int t = *x;
*x = *y;
*y = t;
}
int main(void){
int a = 5, b = 10;
swap(&a, &b); // pass addresses
printf("%d %d", a, b); // β 10 5
}
Key point: passing an address to a function is pass-by-reference. Arrays passed to functions are automatically pass-by-reference for the same reason.
Pointer arithmetic (adding/subtracting pointers)
Adding or subtracting an integer to a pointer moves the address by the size of the type. This is the key to understanding the relationship between arrays and pointers.
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // points to arr[0]printf("%d\n", *p); // 10 (arr[0])printf("%d\n", *(p + 1)); // 20 (arr[1])printf("%d\n", *(p + 3)); // 40 (arr[3])
p += 2; // p now points to arr[2]printf("%d\n", *p); // 30
p + 1 means "the next element", not "the next byte". With an int pointer (4 bytes), p + 1 advances the address by 4 bytes.
10
p+0
20
p+1
30
p+2
40
p+3
50
p+4
Array and pointer equivalence
In C, the following are all equivalent.
Array notation
Pointer notation
Meaning
arr[0]
*arr
First element
arr[i]
*(arr + i)
i-th element
&arr[i]
arr + i
Address of i-th element
Pointer subtraction
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];
int *p2 = &arr[4];
printf("%ld\n", p2 - p1); // 3 (difference in number of elements)
Subtracting two pointers returns "the number of elements between them", not the number of bytes.
The const qualifier β Mark values as "unchangeable"
const tells the compiler and other developers that a variable won't be modified. It prevents bugs and makes code intent clear.
Basic: const variables
constint MAX = 100;
printf("%d\n", MAX); // OK: read freely
MAX = 200; // β Compile error: cannot modify const
Use cases: Values that never change during execution β like pi, array sizes, configuration constants. The advantage over #define is type checking.
Pointers and const β 3 patterns
Combining pointers with const is a notoriously confusing topic. The meaning depends on which side of the asterisk the const appears.
Declaration
What can change
Meaning
const int *p or int const *p
β p can change β *p cannot change
Pointer to a constant value
int * const p
β p cannot change β *p can change
Constant pointer (can't point elsewhere)
const int * const p
β p cannot change β *p cannot change
Fully locked pointer
Concrete examples
int a = 10, b = 20;
// β const int *p : pointed-to value is read-onlyconstint *p1 = &a;
p1 = &b; // β OK: pointer itself can change
*p1 = 30; // β Error: pointed-to value is immutable// β‘ int * const p : pointer itself is read-onlyint * const p2 = &a;
*p2 = 30; // β OK: can modify 'a' through p2
p2 = &b; // β Error: p2 can't be redirected// β’ const int * const p : everything lockedconstint * const p3 = &a;
*p3 = 30; // β Error
p3 = &b; // β Error
Reading trick: Read right-to-left.
γ»const int *p = "p is a pointer to a const int"
γ»int * const p = "p is a const pointer to int"
const in function parameters
When passing arrays or strings to functions that won't modify them, use const to make that intent explicit.
// Just reads the string β mark as constintmy_strlen(constchar *s) {
int n = 0;
while (*s) { s++; n++; }
return n;
}
// Sums array elements, doesn't modify them β constintsum(constint arr[], int n) {
int total = 0;
for (int i = 0; i < n; i++) total += arr[i];
return total;
}
Best practice:Always mark parameters as const when the function doesn't modify them.
γ»Signals intent: "this function won't touch your data"
γ»Accidental modifications are caught at compile time
γ»Standard library functions like strlen, strcmp follow this convention
Important: String literals (strings written directly in source like "Hello") live in read-only memory. Always receive them via const char *.
Summary: const acts as a "bug-prevention barrier". Your code still works without it, but using it catches mistakes at compile time and makes your intent crystal clear.
Try it yourself β pointers
A program that rewrites a value through a pointer. Go ahead and run it.
pointer.c
Output
Press "Run"...
π‘ Try these ideas too
Swap two variables via pointers
Guard with if (p != NULL)
Use pointer arithmetic p + 1
Observe *(&x)
Confirm that *p = 99; rewrites the value of n. That is what it means to "indirectly change a value via a pointer".
Q. Why use pointers? Aren't regular variables enough?
A. Pointers are needed to: 1. Modify a variable's value from a function (pass-by-reference), 2. Dynamically allocate memory, 3. Build complex data structures (lists, trees, etc.), 4. Work with strings, and more. Many features are impossible without pointers.
Q. I keep confusing * and &. Which is which?
A. & (address-of) takes the address indicating "where the variable lives". * (dereference) goes to see "what's stored at this address". In a pointer declaration `int *p;`, the * means "p is a pointer to int".
Q. What is a NULL pointer? How is it different from a dangling pointer?
A. Distinguish these three states: β NULL pointer: a pointer intentionally set to NULL (value 0), meaning "points to nothing." Dereferencing reliably crashes, so it is a safe detectable sentinel. β‘ Uninitialized pointer: only declared, never assigned. Its value is indeterminate (could be anywhere in memory); dereferencing is undefined. β’ Dangling pointer: a pointer that once referred to a valid object, but that object is no longer valid (freed, went out of scope, etc.). The bits may look valid but any access is undefined behavior.
Defensive habits: initialize at declaration (int *p = NULL;), set p = NULL; right after free(p), and never return the address of a local variable.
Q. What does "a pointer to a pointer" mean?
A. A pointer is just a variable too, so the pointer variable itself lives in memory. That means you can make "a pointer to a pointer": `int **pp = &p;`. Multi-level pointers are tricky, but you're just applying the same principle repeatedly. In practice they're rarely needed.
Review Quiz
Check your understanding of this lesson!
Q1. What is stored in p of int *p;?
An integer value
The address of an int variable
A string
int *p is a pointer to int, which stores the memory address of an int variable.
Q2. With int x=10; int *p=&x;, what is the value of *p?
The address of x
10
The address of pointer p
*p dereferences the pointer. Since p points to x, *p = 10.
Q3. What happens when you dereference (*p) a NULL pointer?
Returns 0
Runtime error (segmentation fault)
Compile error
Dereferencing a NULL pointer causes a runtime error such as a segmentation fault. Always NULL-check before use.