C - Pointers
Introduction to Pointers in C
In C programming, a pointer is a variable that stores the memory address of another variable. Pointers enable efficient parameter passing, dynamic memory, array traversal, and systems-level programming.
Learning Objectives
- Declare, initialize, and dereference pointers safely.
- Explain the relationship between variables, addresses, and pointer values.
- Use pointer arithmetic correctly with arrays.
- Modify values via pointers in functions (pass-by-address).
- Allocate and free dynamic memory with
malloc
/free
without leaks.
Prerequisites
Quick Start: Compile and Run
Try this minimal example. Save as pointers_quickstart.c
and compile:
// pointers_quickstart.c
#include <stdio.h>
int main(void) {
int num = 10;
int *ptr = # // ptr stores the address of num
printf("num=%d, &num=%p, ptr=%p, *ptr=%d\n",
num, (void*)&num, (void*)ptr, *ptr);
return 0;
}
gcc -std=c11 -Wall -Wextra -O0 -g pointers_quickstart.c -o pointers_quickstart
./pointers_quickstart
Declaring and Using Pointers
Use the *
operator in the declaration to make a pointer; use &
to take an address, and *
again to dereference (read/write the pointee):
// Declare an integer and a pointer to int
int num = 10;
int *ptr = # // Assign the address of num to ptr
printf("Value of num: %d\n", num); // 10
printf("Address of num: %p\n", (void*)&num); // address (varies)
printf("Value stored in ptr: %p\n", (void*)ptr); // same address
printf("Value pointed by ptr: %d\n", *ptr); // 10
Visualizing Pointers
Pointer ptr stores the address of num and points to its value.
Pointer Arithmetic
Incrementing a pointer moves it by the size of its pointee type:
int arr[3] = {1, 2, 3};
int *p = arr; // same as &arr[0]
printf("%d\n", *p); // 1
p++; // advance by sizeof(int)
printf("%d\n", *p); // 2
Pointers and Functions
Pass an address so the function can modify the caller's variable:
void increment(int *n) {
(*n)++;
}
int main(void) {
int value = 5;
increment(&value);
printf("%d\n", value); // 6
return 0;
}
Real-World Examples
1) Swap Two Numbers Using Pointers
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 10, y = 20;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y); // x = 20, y = 10
return 0;
}
2) Dynamic Memory Allocation
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *arr = malloc((size_t)n * sizeof *arr);
if (!arr) { perror("malloc"); return 1; }
for (int i = 0; i < n; i++) {
arr[i] = i * 2;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
Always check the result of malloc
and free
what you allocate exactly once.
Common Mistakes and Pitfalls
- Dereferencing uninitialized or
NULL
pointers (undefined behavior). Initialize pointers and check forNULL
when applicable. - Returning the address of a local variable (becomes invalid after the function returns).
- Mismatched allocation/deallocation (alloc with
malloc
, free withfree
). Nodelete
in C. - Pointer arithmetic on the wrong type or past array bounds.
- Confusing pointer vs pointee sizes:
sizeof(ptr)
is the size of the pointer itself, not the array.
Checks for Understanding
- What does the
*
mean in a declaration vs. when used in an expression? - Why cast to
(void*)
when printing addresses with%p
? - What does
p++
do whenp
is anint*
pointing into an array?
Show suggested answers
- In a declaration it makes a pointer type; in an expression it dereferences (reads/writes the pointee).
%p
expects avoid*
, so casting is portable and correct.- It advances
p
bysizeof(int)
bytes to the next array element.
Practice Exercises
-
Write a function
divmod_i(int a, int b, int *q, int *r)
that writes quotient and remainder via pointers.Solution
void divmod_i(int a, int b, int *q, int *r) { *q = a / b; *r = a % b; }
-
Allocate an array of N integers on the heap, fill with squares, print them, and free the memory.
Solution
#include <stdio.h> #include <stdlib.h> void print_squares(int n) { int *v = malloc((size_t)n * sizeof *v); if (!v) { perror("malloc"); return; } for (int i = 0; i < n; i++) v[i] = i * i; for (int i = 0; i < n; i++) printf("%d ", v[i]); printf("\n"); free(v); }
Capstone Challenge
Implement a minimal resizable vector of int
with init
, push
, and free
using malloc
/realloc
.
Show reference implementation
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
typedef struct {
int *data;
size_t len;
size_t cap;
} ivec;
bool ivec_init(ivec *v, size_t cap) {
v->len = 0; v->cap = cap ? cap : 1;
v->data = malloc(v->cap * sizeof *v->data);
return v->data != NULL;
}
bool ivec_push(ivec *v, int x) {
if (v->len == v->cap) {
size_t new_cap = v->cap * 2;
int *p = realloc(v->data, new_cap * sizeof *p);
if (!p) return false;
v->data = p; v->cap = new_cap;
}
v->data[v->len++] = x;
return true;
}
void ivec_free(ivec *v) {
free(v->data);
v->data = NULL; v->len = v->cap = 0;
}
int main(void) {
ivec v; if (!ivec_init(&v, 0)) return 1;
for (int i = 0; i < 10; i++) ivec_push(&v, i);
for (size_t i = 0; i < v.len; i++) printf("%d ", v.data[i]);
printf("\n");
ivec_free(&v);
}
Key Takeaways
- Pointers store addresses; dereferencing accesses the value at that address.
- Pointer arithmetic moves in units of the pointee type.
- Passing pointers enables functions to modify caller-owned data.
- Always pair
malloc
withfree
and check forNULL
.