Pointers are one of C’s most powerful and misunderstood features. This guide walks through the essentials: what pointers are, how to use them safely, and common pitfalls.
What is a pointer?
A pointer stores the address of another object (variable, function, or dynamically allocated memory).
#include <stdio.h>
int main(void) {
int x = 42; // a normal integer
int *p = &x; // p points to x
printf("x=%d\n", x); // value
printf("&x=%p\n", (void*)&x); // address of x
printf("p=%p\n", (void*)p); // pointer value (address stored)
printf("*p=%d\n", *p); // dereference → value at address
}
Key ideas:
&x
gives the address ofx
.*p
dereferencesp
to access the object it points to.- The type
int*
means “pointer to int.” Types must match for correct dereferencing.
Pointer types and const
int * p; // pointer to int (modifiable through p)
const int * pc; // pointer to const int (cannot modify *pc through pc)
int * const cp = &x; // const pointer to int (cp cannot change, *cp can)
const int * const cpc = &x; // const pointer to const int
- “const to the left of the star” protects the pointee; “const to the right” fixes the pointer.
Pointer arithmetic (with arrays)
In C, arrays decay to pointers to their first element in many expressions. Pointer arithmetic moves in units of the pointed-to type.
#include <stdio.h>
int main(void) {
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // same as &a[0]
printf("*p=%d\n", *p); // 10
printf("*(p+2)=%d\n", *(p+2)); // 30
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");
}
Rules:
- You may move within the same array (
p
,p+1
, …,p+n
). - Going outside the array (except the one-past-the-end position for comparisons) is undefined behavior.
Pointers and strings
Strings in C are arrays of char
terminated by \0
.
#include <stdio.h>
int main(void) {
char s[] = "hello"; // array with space for '\0'
char *ps = s; // points to first character
for (; *ps != '\0'; ps++) {
putchar(*ps);
}
putchar('\n');
}
Common mistakes:
- Forgetting the terminating
\0
when building strings manually. - Modifying string literals (e.g.,
char *s = "hi"; s[0] = 'H';
is undefined).
Pointers to pointers
Useful for returning allocated memory or modifying caller-owned pointers.
#include <stdlib.h>
#include <stdbool.h>
bool make_buffer(size_t n, int **out) {
int *buf = malloc(n * sizeof *buf);
if (!buf) return false;
*out = buf; // set caller's pointer
return true;
}
Usage:
int *data = NULL;
if (make_buffer(100, &data)) {
// use data[0..99]
free(data);
}
Function pointers
Store addresses of functions to implement callbacks or pluggable behavior.
#include <stdio.h>
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int apply(int (*op)(int,int), int x, int y) { return op(x, y); }
int main(void) {
printf("%d\n", apply(add, 3, 4)); // 7
printf("%d\n", apply(mul, 3, 4)); // 12
}
Dynamic memory and ownership
#include <stdlib.h>
int *make_array(size_t n) {
int *p = malloc(n * sizeof *p);
if (!p) return NULL; // check for allocation failure
return p; // caller owns and must free
}
int main(void) {
size_t n = 1000;
int *arr = make_array(n);
if (!arr) return 1;
// use arr[0..n-1]
free(arr);
}
Guidelines:
- Every successful
malloc/calloc/realloc
must have a matchingfree
. - After
free(p)
, setp = NULL;
to avoid dangling pointers. - Avoid returning pointers to local (stack) variables.
Common pitfalls (and how to avoid them)
-
Dangling pointers
- Cause: using memory after it’s freed or out of scope.
- Fix:
free
once; set toNULL
; never return address of locals.
-
Out-of-bounds access
- Cause: incorrect pointer arithmetic or loop bounds.
- Fix: keep size variables; prefer indexing; validate boundaries.
-
Incorrect
const
usage- Cause: accidentally modifying through a pointer meant to be read-only.
- Fix: use
const
for pointees you do not intend to modify.
-
Mixing types
- Cause: casting to wrong type and dereferencing.
- Fix: keep types consistent; only cast when necessary and correct.
-
Memory leaks
- Cause: losing the only pointer to allocated memory.
- Fix: define clear ownership; pair allocations with frees on all paths.
Debugging tips
- Use sanitizers (
-fsanitize=address,undefined
) and warnings (-Wall -Wextra -Wpedantic
). - Initialize pointers to
NULL
and check before dereferencing. - In complex code, document ownership and lifetime in comments.
See also
Summary
- Pointers store addresses;
*
dereferences;&
takes addresses. - Pointer arithmetic works in element-sized steps and stays within arrays.
const
on the left protects the pointee; on the right fixes the pointer.- Use pointers for dynamic memory, indirection (pointer-to-pointer), and callbacks (function pointers).
- Avoid UB: no out-of-bounds, no dangling pointers, match
malloc
withfree
.