Pointers and Manual Memory
C pointers hold the memory address of a value; manual heap allocation (malloc/free) replaces Python's automatic garbage collection.
Introduction
In this lesson, you'll learn about pointers and manual memory in C. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Python, you're familiar with c pointers hold the memory address of a value; manual heap allocation (malloc/free) replaces python's automatic garbage collection..
C has its own approach to c pointers hold the memory address of a value; manual heap allocation (malloc/free) replaces python's automatic garbage collection., which we'll explore step by step.
The C Way
Let's see how C handles this concept. Here's a typical example:
#include <stdio.h>
#include <stdlib.h> /* malloc, free */
#include <string.h> /* memset */
/* Pass pointer to allow function to modify the caller's variables */
void swap(int *a, int *b) {
int tmp = *a; /* dereference: read value at address */
*a = *b;
*b = tmp;
}
/* Return a heap-allocated array — caller must free() */
int *make_array(int n, int fill) {
int *arr = malloc(n * sizeof(int)); /* allocate n ints on heap */
if (!arr) return NULL; /* malloc can fail! */
for (int i = 0; i < n; i++) arr[i] = fill;
return arr; /* caller owns this memory */
}
int main(void) {
int x = 3, y = 7;
printf("before: x=%d y=%d\n", x, y);
swap(&x, &y); /* pass address of x and y */
printf("after: x=%d y=%d\n", x, y); /* x=7 y=3 */
int *data = make_array(100, 0);
if (!data) { fprintf(stderr, "OOM\n"); return 1; }
data[42] = 99;
printf("data[42]=%d\n", data[42]);
free(data); /* MUST free — no GC */
data = NULL; /* avoid dangling pointer */
return 0;
}Comparing to Python
Here's how you might have written similar code in Python:
# Python — automatic memory, no explicit pointers
def swap(lst, i, j):
lst[i], lst[j] = lst[j], lst[i] # list passed by reference
numbers = [3, 1, 4]
swap(numbers, 0, 2)
print(numbers) # [4, 1, 3]
# Dynamic allocation is implicit
data = [0] * 100 # GC handles cleanup
data = None # refcount drops, memory freed automaticallyYou may be used to different syntax or behavior.
int *p declares a pointer; &x gives the address of x; *p dereferences (reads/writes the pointed-to value)
You may be used to different syntax or behavior.
malloc(n * sizeof(T)) allocates n bytes on the heap; returns void* or NULL on failure
You may be used to different syntax or behavior.
Every malloc must have a paired free() — no garbage collector, leaks are permanent until process exits
You may be used to different syntax or behavior.
Passing &var to a function lets it modify the caller's variable (simulating pass-by-reference)
You may be used to different syntax or behavior.
Python references are automatically reference-counted; C pointers are raw memory addresses with no safety net
Step-by-Step Breakdown
1. Declare and Dereference Pointers
int *p is a pointer to int. Use & to take the address and * to dereference (access the value). Think of * as 'the value at this address'.
x = 42 # Python: x is a name bound to an objectint x = 42;
int *p = &x; /* p holds the address of x */
printf("%d\n", *p); /* dereference: prints 42 */
*p = 99; /* modifies x through p */
printf("%d\n", x); /* 99 */2. Pointer Parameters for Output
C is pass-by-value. To let a function modify a variable, pass its address. The function uses * to write through the pointer.
def swap(lst, i, j):
lst[i], lst[j] = lst[j], lst[i]void swap(int *a, int *b) {
int tmp = *a; *a = *b; *b = tmp;
}
// call:
swap(&x, &y); /* pass addresses */3. Heap Allocation with malloc
malloc allocates memory on the heap; returns void* (cast not required in C). Always check for NULL — malloc fails when out of memory.
data = [0] * n # Python: heap-allocated, GC'dint *data = malloc(n * sizeof(int));
if (!data) { perror("malloc"); exit(1); }
for (int i = 0; i < n; i++) data[i] = 0;
/* ... use data ... */
free(data);
data = NULL;4. Pointer Arithmetic
Adding 1 to a pointer advances it by sizeof(T) bytes — directly accessing the next element in an array.
for item in lst: # Python iteratorint arr[5] = {10, 20, 30, 40, 50};
int *p = arr; /* p points to arr[0] */
for (int i = 0; i < 5; i++) {
printf("%d\n", *(p + i)); /* arr[i] */
}
/* arr[i] and *(arr+i) are identical */Common Mistakes
When coming from Python, developers often make these mistakes:
- int *p declares a pointer; &x gives the address of x; *p dereferences (reads/writes the pointed-to value)
- malloc(n * sizeof(T)) allocates n bytes on the heap; returns void* or NULL on failure
- Every malloc must have a paired free() — no garbage collector, leaks are permanent until process exits
Key Takeaways
- int *p = &x; — p holds x's address; *p reads/writes the value at that address
- Pass &var to functions that need to modify the caller's variable
- malloc allocates heap memory; every malloc needs a free() — no garbage collector
- Pointer arithmetic: p+1 advances by sizeof(*p) bytes — use for array traversal
- After free, set pointer to NULL to prevent dangling-pointer bugs