What is a Pointer? — The Address Idea
Every variable in your program is stored in computer memory. Every memory location has an address — like a house number on a street. A pointer is a variable that stores an address instead of a value.
Normal variable: stores a value like 42 or 3.14.
Pointer variable: stores an address like 0x7fff...a4 — the location of some other variable.
Simple analogy: Your friend's house number written in your diary. The diary entry is the pointer. Your friend's house is the variable. The house number written down is the address.
Memory picture — variable x stored at address 1000, pointer p stores that address
malloc() returns a pointer. Pointers are everywhere in C once you look.Declaring a Pointer — The * Symbol
You declare a pointer by putting a * before the variable name. The data type before the * tells C what type of variable this pointer will point to.
int *p means "p is a pointer to an integer". float *q means "q is a pointer to a float".
data_type *pointer_name; int *p; /* pointer to int */ float *q; /* pointer to float */ char *c; /* pointer to char */ double *d; /* pointer to double */
Two operators work with pointers every time:
&(address-of operator) — placed before a variable, gives its address.&xmeans "the address of x". This is how you make a pointer point at something.*(dereference operator) — placed before a pointer, gives the value at that address.*pmeans "go to the address stored in p and read the value there".
#include <stdio.h> int main() { int x = 42; /* normal variable */ int *p; /* pointer to int — NOT yet pointing anywhere */ p = &x; /* p now holds the ADDRESS of x */ printf("Value of x = %d\n", x); printf("Address of x = %p\n", &x); /* address */ printf("Value of p = %p\n", p); /* p holds address of x */ printf("Value at *p = %d\n", *p); /* dereference: value at address */ /* Changing x through the pointer */ *p = 100; /* goes to the address p holds, writes 100 there */ printf("\nAfter *p = 100:\n"); printf("x is now = %d\n", x); /* x changed! */ return 0; }
Value of x = 42 Address of x = 0x7fff5a4c (example) Value of p = 0x7fff5a4c (same address) Value at *p = 42 After *p = 100: x is now = 100
p = &x means p and x are connected. *p IS x. Changing *p changes x because they are the same memory location. p just holds the address that leads to x.& and * — Two Opposite Operations
& and * are exact opposites:
&x— start with variable x, give me its address*p— start with address p, give me the value stored there
So *(&x) = x. You take the address of x and immediately dereference it — you get x back.
| Expression | Read as | What you get | Example result |
|---|---|---|---|
| x | the variable x | The value stored in x | 42 |
| &x | address of x | The memory address of x | 0x7fff5a4c |
| p | the pointer p | Whatever address p stores | 0x7fff5a4c |
| *p | value at address p | The value stored at p's address | 42 |
| &p | address of p itself | Memory address where p lives | 0x7fff9b00 |
| *(&x) | deref address of x | Same as x — cancel out | 42 |
#include <stdio.h> int main() { int a = 10, b = 20; int *p = &a; /* p points to a */ int *q = &b; /* q points to b */ printf("a = %d, b = %d\n", a, b); printf("*p = %d, *q = %d\n", *p, *q); /* p now points to b instead */ p = &b; printf("After p = &b: *p = %d\n", *p); /* 20 */ /* Change b through p */ *p = 99; printf("After *p=99: b = %d\n", b); /* b changed to 99! */ return 0; }
a = 10, b = 20 *p = 10, *q = 20 After p = &b: *p = 20 After *p=99: b = 99
int *p; *p = 5; — p doesn't point anywhere yet. Reading or writing through it causes a crash (segmentation fault). Always assign a valid address before using *p: either p = &someVariable or p = malloc(...).Pointer Arithmetic — Moving Through Memory
You can add or subtract integers to/from a pointer. When you write p + 1, it doesn't add 1 to the address — it adds sizeof(type) bytes. So for an int *p, p + 1 moves forward by 4 bytes (the size of int). This makes walking through arrays natural and fast.
int arr[4] = {10, 20, 30, 40} — p = &arr[0] — pointer arithmetic
#include <stdio.h> int main() { int arr[4] = {10, 20, 30, 40}; int *p = arr; /* p = &arr[0] — same thing */ int i; printf("Using pointer arithmetic:\n"); for (i = 0; i < 4; i++) { printf("*(p+%d) = %d\n", i, *(p + i)); } printf("\nUsing p++ to walk:\n"); p = arr; /* reset to start */ for (i = 0; i < 4; i++) { printf("%d ", *p); p++; /* move to next element */ } printf("\n"); return 0; }
Using pointer arithmetic: *(p+0) = 10 *(p+1) = 20 *(p+2) = 30 *(p+3) = 40 Using p++ to walk: 10 20 30 40
| Operation | Meaning | For int* (4 bytes) |
|---|---|---|
| p + 1 | Next element | Address + 4 bytes |
| p - 1 | Previous element | Address − 4 bytes |
| p++ | Move to next, return old | Address += 4 |
| ++p | Move to next, return new | Address += 4 |
| p2 - p1 | Number of elements between | (addr2 − addr1) / 4 |
| p1 == p2 | Do they point to same location? | Compare addresses |
Pointers and Arrays — They Are Connected
In C, an array name is a pointer to the first element. arr and &arr[0] are the same thing — the address of the first element. This means arr[i] and *(arr + i) are identical — the compiler converts one into the other automatically.
#include <stdio.h> int main() { int arr[5] = {11, 22, 33, 44, 55}; int i; printf("arr == &arr[0]: %s\n", arr == &arr[0] ? "YES" : "NO"); printf("\nArray notation vs pointer notation:\n"); printf("%-15s %-15s\n", "arr[i]", "*(arr+i)"); printf("------------------------------\n"); for (i = 0; i < 5; i++) printf("arr[%d] = %-8d *(arr+%d) = %d\n", i, arr[i], i, *(arr + i)); return 0; }
arr == &arr[0]: YES Array notation vs pointer notation: arr[i] *(arr+i) ------------------------------ arr[0] = 11 *(arr+0) = 11 arr[1] = 22 *(arr+1) = 22 arr[2] = 33 *(arr+2) = 33 arr[3] = 44 *(arr+3) = 44 arr[4] = 55 *(arr+4) = 55
arr[i] into *(arr+i) internally. This is why arrays in C are so fast — element access is just a simple address calculation.Pointers and Functions — Pass by Reference
Normally C passes a copy of a variable to a function — changing the copy doesn't affect the original. When you pass a pointer (the address), the function can reach back and modify the original variable directly. This is called pass by reference.
#include <stdio.h> /* Correct swap — uses pointers to modify originals */ void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } /* Get min and max in one call — two output pointers */ void minMax(int arr[], int n, int *mn, int *mx) { *mn = *mx = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] < *mn) *mn = arr[i]; if (arr[i] > *mx) *mx = arr[i]; } } int main() { int x = 5, y = 9; printf("Before: x=%d y=%d\n", x, y); swap(&x, &y); /* pass addresses */ printf("After: x=%d y=%d\n", x, y); int arr[] = {34, 7, 89, 12, 56}; int mn, mx; minMax(arr, 5, &mn, &mx); printf("Min=%d Max=%d\n", mn, mx); return 0; }
Before: x=5 y=9 After: x=9 y=5 Min=7 Max=89
scanf uses this exact technique — it takes &variable to write the typed value directly into your variable.NULL Pointer — A Safe Empty Pointer
A NULL pointer is a pointer that points to nothing. It is defined as 0 (or NULL from stdio.h). Use it when you want to say "this pointer is not currently pointing anywhere". You can check if (p == NULL) before using it — this prevents crashes from dereferencing an invalid pointer.
#include <stdio.h> #include <stdlib.h> int main() { int *p = NULL; /* safe empty pointer */ /* Always check before using */ if (p == NULL) { printf("p is NULL — not pointing anywhere\n"); } /* Point it at something */ int x = 55; p = &x; if (p != NULL) { printf("p now points to: %d\n", *p); } /* strstr returns NULL if not found */ char *result = strstr("Hello World", "World"); if (result != NULL) printf("Found: %s\n", result); return 0; }
p is NULL — not pointing anywhere p now points to: 55 Found: World
*p when p == NULL is a segmentation fault. Always initialise pointers to NULL when you declare them, and check before use. Many library functions return NULL on failure — always check.Pointer to Pointer — Double Pointer
A pointer stores an address. But a pointer is itself a variable — it has its own address. You can have a pointer that stores the address of another pointer. This is called a double pointer and is declared with **.
#include <stdio.h> int main() { int x = 42; int *p = &x; /* p holds address of x */ int **pp = &p; /* pp holds address of p */ printf("x = %d\n", x); printf("*p = %d\n", *p); /* value at p = x = 42 */ printf("**pp= %d\n", **pp); /* value at *pp = *p = x = 42 */ /* Modify x through double pointer */ **pp = 999; printf("x after **pp=999: %d\n", x); return 0; }
x = 42 *p = 42 **pp= 42 x after **pp=999: 999
Quick Quiz
What does int *p = &x do?
If int arr[5], what does p + 1 give when int *p = arr?
Are arr[i] and *(arr + i) the same thing?
Why does swap(int *a, int *b) work but swap(int a, int b) does not?
What happens if you dereference a NULL pointer: int *p = NULL; *p = 5;?
Lesson Checklist
- A pointer stores a memory address, not a value
- &x gives the address of x — used to make a pointer point to x
- *p dereferences p — reads or writes the value at the address p holds
- int *p declares a pointer to int — the type tells C how many bytes to read/write
- Never use *p before assigning a valid address to p
- Pointer arithmetic: p+1 moves by sizeof(type) bytes — not 1 byte
- arr[i] and *(arr+i) are identical — array name is a pointer to first element
- Pass &variable to a function to let it modify the original (pass by reference)
- NULL pointer = safe empty pointer — always check != NULL before dereferencing
- I completed the quiz