Day 2 of 5
⏱ ~60 minutes
C Programming in 5 Days — Day 2

Pointers & Memory Management

Pointers are C's most powerful and most dangerous feature. They let you manipulate memory directly, build efficient data structures, and interface with hardware. Today you master pointer arithmetic, dynamic allocation, and memory safety.

Pointers Fundamentals

A pointer stores a memory address. 'int *p = &x' makes p point to x. '*p' dereferences (reads/writes the value at the address). Pointer arithmetic: p+1 advances by sizeof(*p) bytes, not 1 byte. An int pointer advances by 4; a char pointer by 1. 'int a[10]; int *p = a;' — p and a are equivalent here. Arrays are syntactic sugar for pointer arithmetic: a[i] is *(a+i).

Dynamic Memory with malloc/free

malloc(n) allocates n bytes from the heap and returns a void*. Always check for NULL (allocation failure). free(ptr) returns memory to the heap. Rules: every malloc needs exactly one free; never free the same pointer twice (double-free is a security vulnerability); never use a pointer after freeing it (use-after-free). calloc(n, size) allocates and zeroes. realloc(ptr, new_size) resizes an allocation.

Common Memory Errors

The most dangerous C bugs: buffer overflow (write past array end), buffer over-read (read past array end — Heartbleed was this), use-after-free, double-free, memory leak (allocate but never free), and null pointer dereference. Use Valgrind to detect all of these at runtime: 'valgrind --leak-check=full ./program'. AddressSanitizer (compile with -fsanitize=address) catches these at near-native speed.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Dynamic array: grow as needed
typedef struct {
    int   *data;
    size_t len;
    size_t cap;
} IntVec;

void vec_push(IntVec *v, int val) {
    if (v->len == v->cap) {
        v->cap = v->cap ? v->cap * 2 : 4;
        v->data = realloc(v->data, v->cap * sizeof(int));
        if (!v->data) { perror("realloc"); exit(1); }
    }
    v->data[v->len++] = val;
}

void vec_free(IntVec *v) {
    free(v->data);
    v->data = NULL;
    v->len = v->cap = 0;
}

int main(void) {
    IntVec v = {0};
    for (int i = 0; i < 20; i++) vec_push(&v, i * i);
    for (size_t i = 0; i < v.len; i++) printf("%d ", v.data[i]);
    printf("\n");
    vec_free(&v);
    return 0;
}
💡
Set freed pointers to NULL immediately: 'free(ptr); ptr = NULL;'. This turns a silent use-after-free bug into an immediate null pointer dereference segfault — much easier to debug.
📝 Day 2 Exercise
Implement a Linked List
  1. Define a Node struct with an int value and a Node* next pointer
  2. Write list_append(head, value) that allocates a new node with malloc
  3. Write list_print(head) that traverses and prints all values
  4. Write list_free(head) that frees every node in the list
  5. Run your program under Valgrind to confirm zero memory leaks

Day 2 Summary

  • Pointers store addresses; *ptr dereferences; &var takes address
  • Pointer arithmetic advances by sizeof(*ptr) bytes per unit
  • malloc/calloc/realloc allocate heap memory; free returns it
  • Every malloc needs exactly one free — never double-free or use-after-free
  • Valgrind and AddressSanitizer (-fsanitize=address) detect all memory errors
Challenge

Implement a hash table in C with separate chaining. Support: insert(key, value), lookup(key), delete(key), and free_table(). Handle collisions correctly and test with at least 1,000 key-value pairs.

Finished this lesson?