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/freewithout 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 NULLpointers (undefined behavior). Initialize pointers and check forNULLwhen applicable.
- Returning the address of a local variable (becomes invalid after the function returns).
- Mismatched allocation/deallocation (alloc with malloc, free withfree). Nodeletein 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 whenpis 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).
- %pexpects a- void*, so casting is portable and correct.
- It advances pbysizeof(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.Solutionvoid 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 mallocwithfreeand check forNULL.