Pointers in C
1. What is a Pointer? (Very Simple Explanation)
A pointer is a variable that stores the memory address of another variable.
Example:
If a normal variable stores a value → 10
A pointer stores where that value is kept in memory → like 0x7ffeefb8d9c.
So:
Normal variable → stores data
Pointer variable → stores address of data
2. Why Do We Use Pointers?
Pointers are used because they allow us to:
✔ Access memory directly
✔ Share data between functions
✔ Create dynamic memory (malloc/free)
✔ Handle arrays and strings efficiently
✔ Create data structures like Linked List, Tree, Stack, Queue
3. Declaring a Pointer
Syntax:
int *ptr;
Meaning:
ptr is a pointer that can store the address of an int.
4. Storing Address in Pointer
int a = 10;
int *p = &a;
Explanation:
a stores 10
&a gives address of a
p stores that address
Memory Visualization
a = 10
Address of a → 1000
p stores 1000
p → 1000 → 10
5. Dereferencing a Pointer (*)
Dereferencing means accessing the value stored at the pointer’s address.
printf("%d", *p);
*p gives the value → 10
Basic declaration, address, dereference (diagram + example)
Code
#include <stdio.h>
int main() {
int a = 10; // a holds value 10
int *p = &a; // p holds address of a
printf("a = %d\n", a);
printf("&a = %p\n", (void*)&a);
printf("p = %p\n", (void*)p);
printf("*p = %d\n", *p); // value at address stored in p
return 0;
}
Memory diagram (conceptual)
Address Variable Value
--------------------------------
0x1000 a 10
0x2000 p 0x1000 <-- p stores address of a
Visual:
p (0x2000) ---points to---> [ a @0x1000 ] ---> value 10
deref: *p == 10
Key points
• p == &a
• *p reads/writes a's value (*p = 20; sets a = 20).
3. Pointer arithmetic (why it moves by sizeof(type))
Idea
When you increment a pointer p++, it moves by sizeof(*p) bytes (so an int* typically moves by 4
bytes on many systems, double* by 8, char* by 1).
Example
int arr[] = {10, 20, 30};
int *p = arr; // arr == &arr[0]
printf("%d\n", *p); // 10
p++;
printf("%d\n", *p); // 20 (pointer moved to next element)
Diagram
Addresses: 0x1000 0x1004 0x1008
arr[0] 10 20 30
p --> [10] ---> [20] ---> [30]
4. Arrays and pointers — close relationship
• arr (in most expressions) decays to a pointer to the first element &arr[0].
• arr[i] is the same as *(arr + i).
Example: iterate using a pointer
for (int *q = arr; q < arr + 3; q++) {
printf("%d ", *q);
}
This prints 10 20 30.
5. Pointer to pointer (double pointer) — diagrams + example
Code
int a = 5;
int *p = &a; // p -> a
int **pp = &p; // pp -> p -> a
printf("%d\n", **pp); // 5
Diagram
Address Variable Value
--------------------------------
0x1000 a 5
0x2000 p 0x1000
0x3000 pp 0x2000
pp -> p -> a -> 5
*pp == p ; **pp == a's value
Use-cases
• Passing pointer by reference (function modifies caller’s pointer).
• Dynamic arrays of pointers, multi-dimensional arrays represented as pointer-to-pointer.
6. Pointers and functions — call by reference
To let a function change a variable from the caller, pass its pointer.
Swap example
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
int main() {
int a = 3, b = 7;
swap(&a, &b); // pass addresses
// now a == 7 and b == 3
}
Diagram during swap(&a, &b):
main stack: a->3, b->7
swap receives x->address_of_a, y->address_of_b
*x and *y modify original variables
7. Dynamic memory (malloc/free) — heap vs stack
Key points
• malloc, calloc, realloc allocate memory on the heap; returns pointer.
• free(ptr) releases it.
• Use after allocation: check for NULL (allocation failure).
Example
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int *p = malloc(n * sizeof *p);
if (!p) { perror("malloc"); return 1; }
for (int i = 0; i < n; i++) p[i] = i * 10;
for (int i = 0; i < n; i++) printf("%d ", p[i]);
free(p); // important
return 0;
}
Diagram
Stack: p (holds address 0x5000)
Heap at 0x5000: [0] [1] [2] [3] [4] (allocated block)
Common errors: forgetting free → memory leak; using freed memory → dangling pointer.
8. Strings & pointers
A C string is char[] ending with '\0'. char *s = "HELLO"; points to string literal (usually read-
only).
Example
char s[] = "HELLO"; // modifiable array on stack
char *t = "WORLD"; // pointer to string literal (don't modify)
printf("%c %c\n", s[1], *(t + 2)); // E, R
Diagram:
Memory:
s -> 'H' 'E' 'L' 'L' 'O' '\0' (modifiable)
t -> "WORLD\0" (in read-only area)
9. Structs and pointers
Pointer to struct is common: struct Node *p; and access via p->member (same as (*p).member).
Example: simple node
struct Node {
int data;
struct Node *next;
};
Diagram for one node
heap block: [ data: 10 | next: 0x0000 ] // a single node with next NULL
pointer head -> that block
10. Function pointers
A pointer can point to a function; useful for callbacks, dispatch tables.
Example
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
int (*op)(int,int) = add;
printf("%d\n", op(5,3)); // 8
op = sub;
printf("%d\n", op(5,3)); // 2
}
11. Advanced: pointer casting & void *
• void * is a generic pointer (no type). You can cast to/from void*.
• Use void* with malloc and for APIs that take generic data.
void *vp = malloc(100);
int *ip = (int*) vp;
Prefer int *ip = malloc(100 * sizeof *ip); (no cast in C and safer style).
12. Common pitfalls — illustrated and explained
1 Uninitialized (wild) pointer
int *p; // contains garbage address
*p = 5; // undefined behavior — likely crash
Always initialize: int *p = NULL; or p = &x;.
2 Null pointer dereference
int *p = NULL;
printf("%d", *p); // crash (segfault)
Always check if (p != NULL) before deref.
3 Dangling pointer
int *p = malloc(sizeof *p);
free(p);
printf("%d", *p); // undefined — p is dangling
After free, set p = NULL;.
4 Buffer overflow
Writing past allocated memory corrupts memory:
int *p = malloc(3 * sizeof *p);
p[3] = 10; // out-of-bounds (valid indices 0..2)
5 Memory leak
Losing pointer to heap block without free:
p = malloc(...);
p = malloc(...); // previous block lost => leak
6 Wrong sizeof usage
Use malloc(n * sizeof *p) or malloc(n * sizeof(int)). Do not use sizeof(p) to get element
size — sizeof(p) gives pointer size, not *p.
Practice examples (try/run these)
Example A — Reverse array using pointers
#include <stdio.h>
void reverse(int *a, int n) {
int *l = a, *r = a + n - 1;
while (l < r) {
int t = *l; *l = *r; *r = t;
l++; r--;
}
}
Example B — allocate 2D array dynamically (pointer-to-pointer)
int **mat = malloc(rows * sizeof *mat);
for (i = 0; i < rows; ++i)
mat[i] = malloc(cols * sizeof *mat[i]);
(Remember to free each row then free mat.)
Example C — function pointer usage (simple strategy pattern)
int operate(int (*f)(int,int), int a, int b) { return f(a,b); }
15. Quick reference table
Symbol Meaning
&x address of x
*p value pointed by p
T *p p is pointer to type T
p++ move pointer to next T element (+ sizeof(T) bytes)
NULL pointer that points to nothing
void* untyped pointer (generic)
p->m shorthand for (*p).m when p points to struct