Pointers — 10 More Examples
0%
Pointers  ·  10 Examples

Pointers in C —
10 Programs

Ten programs from first address to function pointers — declaration, dereferencing, pointer arithmetic, arrays, strings, structs, dynamic memory, double pointers, and callbacks. Every essential pointer technique in one chapter.

1
First Pointer
2
Swap via Ptr
3
Ptr Arithmetic
4
Ptr & Arrays
5
Ptr & Strings
6
Ptr & Structs
7
Dynamic Mem
8
Double Pointer
9
Fn Pointers
10
Full App
1
📌 Your First Pointer — Address & Dereference
Declare a pointer, store an address with &, read and write through it with *
Basics
A pointer is a variable that stores a memory address. You get the address of any variable with the address-of operator &. You read or write the value at that address with the dereference operator *. The type of a pointer matters — int *p tells the compiler that the address in p points to an int, so *p reads exactly 4 bytes and interprets them as an integer.
ex1_first_pointer.c
C
#include <stdio.h>

int main() {
    int  x = 42;
    int *p = &x;      /* p holds the address of x */

    printf("x        = %d\n",   x);
    printf("&x       = %p\n",   (void*)&x);
    printf("p        = %p\n",   (void*)p);
    printf("*p       = %d\n",   *p);       /* dereference: read x via p */

    /* Modify x through the pointer */
    *p = 100;
    printf("\nAfter *p = 100:\n");
    printf("x  = %d\n", x);   /* x is now 100 */
    printf("*p = %d\n", *p);  /* same thing */

    printf("\nsizeof(int)   = %zu bytes\n", sizeof(int));
    printf("sizeof(int *) = %zu bytes\n", sizeof(int*));
    return 0;
}
output
x        = 42
&x       = 0x7ffd1004
p        = 0x7ffd1004
*p       = 42

After *p = 100:
x  = 100
*p = 100

sizeof(int)   = 4 bytes
sizeof(int *) = 8 bytes
memory — p stores the address of x; *p reads the value there
x @ 0x1004
100
← 4 bytes (int)
p @ 0x1008
0x1004
100
← 8 bytes (address)
Every pointer is the same size on a given machine — 8 bytes on 64-bit systems — regardless of what type it points to. int *, double *, and char * all occupy 8 bytes. The type controls how many bytes are read when you dereference, not how big the pointer itself is.
example 2
2
🔄 Swap via Pointers — Pass by Reference
The classic demonstration: why pointers are needed to modify caller variables
Pass by Ref
C passes all arguments by value — the function gets a copy, and changes to the copy don't affect the original. To actually modify a caller's variable, you pass its address (a pointer). Inside the function, you dereference the pointer to read and write the original. The swap function here is the canonical example — it takes two int * arguments and exchanges the values at those addresses.
ex2_swap.c
C
#include <stdio.h>

/* WRONG — swaps copies, caller unchanged */
void swapWrong(int a, int b) {
    int tmp = a; a = b; b = tmp;
}

/* CORRECT — swaps via pointers */
void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main() {
    int x = 10, y = 20;

    printf("Before          : x=%d  y=%d\n", x, y);

    swapWrong(x, y);
    printf("After swapWrong : x=%d  y=%d\n", x, y);

    swap(&x, &y);
    printf("After swap      : x=%d  y=%d\n", x, y);
    return 0;
}
output
Before          : x=10  y=20
After swapWrong : x=10  y=20
After swap      : x=20  y=10
swapWrong receives copies of x and y. Swapping those copies has zero effect on the originals. swap receives the addresses of x and y — dereferencing and writing through those addresses changes the actual variables in the caller's stack frame. This is the foundational reason C needs pointers.
example 3
3
➕ Pointer Arithmetic — Walking Memory
Increment a pointer by 1 — it jumps by sizeof(type), not by 1 byte
Arithmetic
When you add 1 to a pointer, it does not advance by 1 byte — it advances by sizeof(*ptr) bytes, so it lands on the next element of that type. An int * advances 4 bytes per step; a double * advances 8. This is what makes walking an array with a pointer clean and efficient. Subtraction of two pointers of the same type gives the number of elements between them.
ex3_ptr_arithmetic.c
C
#include <stdio.h>

int main() {
    int arr[] = { 10, 20, 30, 40, 50 };
    int *p = arr;   /* points to arr[0] */

    printf("%-6s %-14s %s\n", "p", "address", "*p");
    printf("%s\n", "-------------------------------");
    for (int i = 0; i < 5; i++) {
        printf("p+%d    %p    %d\n", i, (void*)(p+i), *(p+i));
    }

    printf("\n--- arithmetic ---\n");
    int *start = arr;
    int *end   = arr + 4;
    printf("end - start = %td elements\n", end - start);
    printf("(char*)end - (char*)start = %td bytes\n",
           (char*)end - (char*)start);

    printf("\n--- walk with p++ ---\n");
    p = arr;
    while (p <= end) {
        printf("%d ", *p++);
    }
    printf("\n");
    return 0;
}
output
p      address          *p
-------------------------------
p+0    0x7ffd1000    10
p+1    0x7ffd1004    20
p+2    0x7ffd1008    30
p+3    0x7ffd100c    40
p+4    0x7ffd1010    50

--- arithmetic ---
end - start = 4 elements
(char*)end - (char*)start = 16 bytes

--- walk with p++ ---
10 20 30 40 50
pointer steps by sizeof(int) = 4 bytes each increment
arr[0..4]
10
20
30
40
50
← each cell = 4 bytes
p steps
+0
+4
+8
+12
+16
← byte offsets from p
p + i is identical to &arr[i]. And *(p + i) is identical to arr[i]. The array subscript [] is literally syntactic sugar for pointer arithmetic — the compiler converts arr[i] into *(arr + i) internally.
example 4
4
📐 Pointers & Arrays — Two Ways to Traverse
Index notation and pointer notation produce identical machine code
Arrays
An array name in C is a constant pointer to its first element. You can traverse an array using index notation (arr[i]) or pointer notation (*(ptr + i) or *ptr++) — the compiler generates identical machine code for both. Understanding this equivalence is essential for reading C standard library code, parsing buffers, and writing efficient loops. Here we reverse an array in-place using a two-pointer technique.
ex4_ptr_arrays.c
C
#include <stdio.h>

void printArr(int *p, int n) {
    for (int i = 0; i < n; i++)
        printf("%d ", p[i]);    /* index on a pointer works fine */
    printf("\n");
}

/* Reverse in-place using two-pointer technique */
void reverse(int *arr, int n) {
    int *lo = arr;
    int *hi = arr + n - 1;
    while (lo < hi) {
        int tmp = *lo;
        *lo++ = *hi;
        *hi-- = tmp;
    }
}

int main() {
    int nums[] = { 5, 3, 8, 1, 9, 2, 7 };
    int n = sizeof(nums) / sizeof(nums[0]);

    printf("Original : "); printArr(nums, n);
    reverse(nums, n);
    printf("Reversed : "); printArr(nums, n);

    /* Show that arr[i] == *(arr+i) */
    printf("\n--- Equivalence ---\n");
    int a[] = { 10, 20, 30 };
    int *p  = a;
    printf("a[1]     = %d\n", a[1]);
    printf("*(a+1)   = %d\n", *(a+1));
    printf("p[1]     = %d\n", p[1]);
    printf("*(p+1)   = %d\n", *(p+1));
    return 0;
}
output
Original : 5 3 8 1 9 2 7
Reversed : 7 2 9 1 8 3 5

--- Equivalence ---
a[1]     = 20
*(a+1)   = 20
p[1]     = 20
*(p+1)   = 20
The array name is not a variable — it is a constant address. You cannot write arr++ because arr itself is not a pointer variable; it is fixed to the start of the array. Copy it into a pointer variable first (int *p = arr) and then you can increment p freely.
example 5
5
🔤 Pointers & Strings — Walk a C String
A C string is a char array; a char* pointer walks it one character at a time
Strings
A C string is just an array of char terminated by a '\0' null byte. A char * pointer points to the first character. Incrementing it steps one byte at a time through the string. The null terminator '\0' (value 0, which is falsy) is the natural loop condition: while (*p) keeps running until the pointer reaches the end. Here we reimplement strlen, strcpy, and string reversal using only pointer arithmetic — no index variable needed.
ex5_ptr_strings.c
C
#include <stdio.h>

/* strlen using pointer walk */
int myStrlen(const char *s) {
    const char *p = s;
    while (*p) p++;       /* stop at '\0' */
    return (int)(p - s);  /* pointer difference = length */
}

/* strcpy using pointer walk */
void myStrcpy(char *dst, const char *src) {
    while ((*dst++ = *src++));   /* copy including '\0' */
}

/* count vowels */
int countVowels(const char *s) {
    int n = 0;
    for (; *s; s++) {
        char c = *s | 0x20;    /* to lowercase */
        if (c=='a'||c=='e'||c=='i'||c=='o'||c=='u') n++;
    }
    return n;
}

int main() {
    const char *msg = "Ananta Code Academy";
    char  buf[50];

    printf("String  : \"%s\"\n", msg);
    printf("Length  : %d\n",    myStrlen(msg));
    printf("Vowels  : %d\n",    countVowels(msg));

    myStrcpy(buf, msg);
    printf("Copy    : \"%s\"\n", buf);

    /* Walk character by character */
    printf("Chars   : ");
    for (const char *p = msg; *p; p++)
        printf("%c", *p);
    printf("\n");
    return 0;
}
output
String  : "Ananta Code Academy"
Length  : 19
Vowels  : 8
Copy    : "Ananta Code Academy"
Chars   : Ananta Code Academy
while (*p) is idiomatic C for "walk until null terminator". Since '\0' has value 0, which is false, the loop exits naturally at the end of the string. The compact copy idiom while ((*dst++ = *src++)) copies the character, advances both pointers, and tests the copied value — stopping when the null byte is copied.
example 6
6
🏗️ Pointers & Structs — Arrow Operator
Point to a struct, use -> to access fields, build a linked list node
Structs
When you have a pointer to a struct, the arrow operator -> accesses its fields: p->field is exactly equivalent to (*p).field. The arrow is preferred because it is cleaner and harder to get wrong. Pointers to structs are everywhere in C: linked lists, trees, function parameters, dynamic arrays. Here we build a three-node singly linked list using struct pointers and walk it with the arrow operator.
ex6_ptr_structs.c
C
#include <stdio.h>

typedef struct Node {
    int          data;
    struct Node *next;   /* pointer to the same struct type */
} Node;

void printList(Node *head) {
    printf("List: ");
    while (head != NULL) {
        printf("%d", head->data);
        if (head->next) printf(" -> ");
        head = head->next;    /* advance via pointer */
    }
    printf(" -> NULL\n");
}

int main() {
    /* Three nodes on the stack */
    Node n3 = { 30, NULL };
    Node n2 = { 20, &n3  };
    Node n1 = { 10, &n2  };

    printList(&n1);

    /* Arrow vs dot */
    Node  val = n1;
    Node *ptr = &n1;
    printf("\nDot   val.data      = %d\n", val.data);
    printf("Arrow ptr->data     = %d\n", ptr->data);
    printf("Equiv (*ptr).data   = %d\n", (*ptr).data);

    /* Modify through pointer */
    ptr->data = 99;
    printf("\nAfter ptr->data=99:\n");
    printList(&n1);
    return 0;
}
output
List: 10 -> 20 -> 30 -> NULL

Dot   val.data      = 10
Arrow ptr->data     = 10
Equiv (*ptr).data   = 10

After ptr->data=99:
List: 99 -> 20 -> 30 -> NULL
p->field is always preferred over (*p).field. They compile to the same code but the arrow is cleaner, more readable, and less prone to operator-precedence mistakes. In real code you will almost never see (*p).field — the arrow operator exists precisely to replace it.
example 7
7
🧠 Dynamic Memory — malloc, realloc, free
Allocate memory at runtime, grow it with realloc, always free when done
Dynamic Mem
Stack memory is fixed at compile time. Dynamic memory is requested at runtime from the heap using malloc (allocate), calloc (allocate and zero), and realloc (resize). It must be explicitly released with free — failing to do so causes a memory leak. The pointer returned by malloc is the only way to reach that memory; losing it before calling free leaks the allocation forever.
ex7_dynamic_memory.c
C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n = 5;

    /* malloc — allocate n ints on the heap */
    int *arr = (int*)malloc(n * sizeof(int));
    if (!arr) { fprintf(stderr, "malloc failed\n"); return 1; }

    for (int i = 0; i < n; i++) arr[i] = (i+1) * 10;

    printf("malloc  (%d ints): ", n);
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    /* realloc — grow to 8 ints */
    n = 8;
    arr = (int*)realloc(arr, n * sizeof(int));
    if (!arr) { fprintf(stderr, "realloc failed\n"); return 1; }
    for (int i = 5; i < n; i++) arr[i] = (i+1) * 10;

    printf("realloc (%d ints): ", n);
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    /* calloc — allocate and zero */
    int *zeros = (int*)calloc(4, sizeof(int));
    printf("calloc  (4 ints): ");
    for (int i = 0; i < 4; i++) printf("%d ", zeros[i]);
    printf("\n");

    free(arr);    /* always free heap memory */
    free(zeros);
    printf("\nAll heap memory freed.\n");
    return 0;
}
output
malloc  (5 ints): 10 20 30 40 50
realloc (8 ints): 10 20 30 40 50 60 70 80
calloc  (4 ints): 0 0 0 0

All heap memory freed.
Every malloc/calloc/realloc must be matched by exactly one free. Double-free is undefined behaviour. Freeing a stack variable is undefined behaviour. Always check the return value of malloc — it returns NULL if allocation fails. After freeing, set the pointer to NULL to prevent use-after-free bugs: free(p); p = NULL;
example 8
8
🔗 Double Pointer — Pointer to a Pointer
int** lets a function allocate memory and hand the pointer back to the caller
Double Ptr
A double pointer (int **pp) is a pointer that holds the address of another pointer. It is needed when a function must change which address a pointer variable holds — for example, a function that allocates memory and stores the result in the caller's pointer variable. It is also how 2-D dynamic arrays are built: an array of int * rows, each row itself a dynamically allocated array of int.
ex8_double_pointer.c
C
#include <stdio.h>
#include <stdlib.h>

/* Allocate an array and return it through a double pointer */
void allocArray(int **out, int n) {
    *out = (int*)malloc(n * sizeof(int));
    for (int i = 0; i < n; i++) (*out)[i] = i * i;
}

/* Build a 2-D dynamic array */
int** make2D(int rows, int cols) {
    int **mat = (int**)malloc(rows * sizeof(int*));
    for (int r = 0; r < rows; r++) {
        mat[r] = (int*)malloc(cols * sizeof(int));
        for (int c = 0; c < cols; c++)
            mat[r][c] = r * cols + c + 1;
    }
    return mat;
}

int main() {
    /* Double pointer to receive array from function */
    int *arr = NULL;
    allocArray(&arr, 5);
    printf("Squares: ");
    for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
    printf("\n");
    free(arr);

    /* 2-D dynamic array */
    int rows=3, cols=4;
    int **mat = make2D(rows, cols);
    printf("\n2-D array (%dx%d):\n", rows, cols);
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++)
            printf("%3d", mat[r][c]);
        printf("\n");
        free(mat[r]);
    }
    free(mat);
    return 0;
}
output
Squares: 0 1 4 9 16

2-D array (3x4):
  1  2  3  4
  5  6  7  8
  9 10 11 12
int** mat — array of pointers, each pointing to a row
mat (int**)
row0*
row1*
row2*
← 3 pointers
mat[0] (int*)
1
2
3
4
← 4 ints heap
mat[1] (int*)
5
6
7
8
← 4 ints heap
Free a 2-D dynamic array in reverse order: free each row first, then free the array of row pointers. Freeing mat first loses the row pointers — those allocations can never be freed and become permanent leaks.
example 9
9
🎯 Function Pointers — Callbacks & Dispatch
Store a function's address in a variable — call different functions at runtime
Fn Pointers
A function pointer stores the address of a function. You can call whichever function the pointer currently points to — enabling callbacks, strategy patterns, and runtime dispatch. The syntax is return_type (*name)(param_types). Function pointers are how qsort accepts a custom comparator, how signal handlers are registered, and how plugin architectures work in C. Here we build a simple calculator and a sort-order selector using function pointers.
ex9_fn_pointers.c
C
#include <stdio.h>
#include <stdlib.h>

/* Four arithmetic functions with the same signature */
double add (double a, double b) { return a + b; }
double sub (double a, double b) { return a - b; }
double mul (double a, double b) { return a * b; }
double dvd (double a, double b) { return b ? a / b : 0; }

/* Dispatch table — array of function pointers */
typedef double (*Op)(double, double);

/* Comparators for qsort */
int ascending (const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}
int descending(const void *a, const void *b) {
    return *(int*)b - *(int*)a;
}

void printArr(int *a, int n) {
    for (int i=0;i<n;i++) printf("%d ",a[i]); printf("\n");
}

int main() {
    /* Dispatch table */
    Op ops[]        = { add, sub, mul, dvd };
    const char *sym[] = { "+", "-", "*", "/" };
    double a = 12.0, b = 4.0;

    printf("--- Calculator ---\n");
    for (int i = 0; i < 4; i++)
        printf("%.0f %s %.0f = %.2f\n", a, sym[i], b, ops[i](a, b));

    /* Callback to qsort */
    int nums[] = { 5, 2, 8, 1, 9, 3 };
    int n = sizeof(nums)/sizeof(nums[0]);

    printf("\n--- qsort with callbacks ---\n");
    qsort(nums, n, sizeof(int), ascending);
    printf("Ascending : "); printArr(nums, n);
    qsort(nums, n, sizeof(int), descending);
    printf("Descending: "); printArr(nums, n);
    return 0;
}
output
--- Calculator ---
12 + 4 = 16.00
12 - 4 = 8.00
12 * 4 = 48.00
12 / 4 = 3.00

--- qsort with callbacks ---
Ascending : 1 2 3 5 8 9
Descending: 9 8 5 3 2 1
A dispatch table (array of function pointers) replaces a long chain of if/else or switch statements. Adding a new operation only requires adding a function and a table entry — the dispatch loop never changes. This is the C equivalent of a virtual function table and the foundation of polymorphism in C.
example 10
10
🏗️ Full App — Dynamic Student Registry
malloc + struct pointers + function pointers + sort + search in one program
Complete App
Every pointer concept from Examples 1–9 in one real application. A student registry allocates its record array dynamically with malloc and grows it with realloc. Each student is accessed through a struct pointer. A function pointer selects the sort comparator at runtime (by name or by GPA). A double pointer lets addStudent update the caller's array pointer after realloc. The whole program is a miniature in-memory database using only pointers.
ex10_dynamic_registry.c
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int   roll;
    char  name[25];
    float gpa;
} Student;

/* Comparators for qsort */
int cmpByName(const void *a, const void *b) {
    return strcmp(((const Student*)a)->name,
                   ((const Student*)b)->name);
}
int cmpByGPA(const void *a, const void *b) {
    float ga = ((const Student*)a)->gpa;
    float gb = ((const Student*)b)->gpa;
    return (ga < gb) - (ga > gb);   /* descending */
}

/* Double pointer: may realloc the caller's array */
void addStudent(Student **arr, int *count, int roll,
                 const char *name, float gpa) {
    *arr = (Student*)realloc(*arr, (*count + 1) * sizeof(Student));
    Student *s = &(*arr)[*count];
    s->roll = roll;
    strncpy(s->name, name, 24);
    s->gpa  = gpa;
    (*count)++;
}

Student* findByRoll(Student *arr, int n, int roll) {
    for (int i = 0; i < n; i++)
        if (arr[i].roll == roll) return &arr[i];
    return NULL;
}

void printAll(Student *arr, int n) {
    printf("%-5s %-20s GPA\n", "Roll", "Name");
    printf("%s\n", "--------------------------------");
    for (int i = 0; i < n; i++)
        printf("%-5d %-20s %.2f\n",
               arr[i].roll, arr[i].name, arr[i].gpa);
}

int main() {
    Student *reg   = NULL;
    int      count = 0;

    /* Add students via double pointer */
    addStudent(&reg, &count, 103, "Priya Negi",   9.1f);
    addStudent(&reg, &count, 101, "Ananya Sharma",8.9f);
    addStudent(&reg, &count, 104, "Karan Mehra",  7.4f);
    addStudent(&reg, &count, 102, "Rohan Verma",  8.2f);
    addStudent(&reg, &count, 105, "Sunita Devi",  6.8f);

    /* Sort by name using function pointer */
    printf("--- Sorted by Name ---\n");
    qsort(reg, count, sizeof(Student), cmpByName);
    printAll(reg, count);

    /* Sort by GPA using different function pointer */
    printf("\n--- Sorted by GPA (desc) ---\n");
    qsort(reg, count, sizeof(Student), cmpByGPA);
    printAll(reg, count);

    /* Search via struct pointer */
    printf("\n--- Search roll 103 ---\n");
    Student *found = findByRoll(reg, count, 103);
    if (found)
        printf("Found: %s  GPA %.2f\n", found->name, found->gpa);

    free(reg);
    return 0;
}
output
--- Sorted by Name ---
Roll  Name                 GPA
--------------------------------
101   Ananya Sharma        8.90
104   Karan Mehra          7.40
103   Priya Negi           9.10
102   Rohan Verma          8.20
105   Sunita Devi          6.80

--- Sorted by GPA (desc) ---
Roll  Name                 GPA
--------------------------------
103   Priya Negi           9.10
101   Ananya Sharma        8.90
102   Rohan Verma          8.20
104   Karan Mehra          7.40
105   Sunita Devi          6.80

--- Search roll 103 ---
Found: Priya Negi  GPA 9.10
All 9 pointer patterns in one program: & address-of · * dereference · pointer arithmetic for array walk · -> arrow operator for struct fields · malloc/realloc/free for heap · double pointer Student** to let addStudent update the caller's pointer · function pointers as comparators passed to qsort. This is the template for every real C data-management program.
checklist
  • Ex 1 — &x gives the address of x. *p reads the value at that address. All pointers are 8 bytes on 64-bit systems.
  • Ex 2 — Pass &var to modify the caller's variable. Passing by value gives the function a copy — changes are invisible to the caller.
  • Ex 3 — p + 1 advances by sizeof(*p) bytes, not 1 byte. end - start gives element count, not byte count.
  • Ex 4 — arr[i] == *(arr + i). The array name is a constant pointer. Copy it to int *p = arr to increment freely.
  • Ex 5 — A C string is a char* to the first character. while (*p) walks to '\0'. Pointer difference gives string length.
  • Ex 6 — p->field == (*p).field. Always prefer the arrow. Struct pointers are used for linked lists, trees, and passing large structs.
  • Ex 7 — malloc allocates, realloc resizes, calloc allocates zeroed. Every allocation needs exactly one free. Check for NULL return.
  • Ex 8 — int **pp lets a function update the caller's pointer variable. Free each row before freeing the pointer array in 2-D dynamic arrays.
  • Ex 9 — Function pointer syntax: ret (*name)(params). Dispatch tables replace long if/switch chains. Callbacks enable runtime behaviour selection.
  • Ex 10 — Full app: double pointer for realloc-safe add · struct pointer + arrow · function pointers for qsort · heap memory with single free at end.