Module 5.1

Introduction to Pointers

Unlock the true power of C programming with pointers! Learn how to work directly with memory addresses, manipulate data efficiently, and understand why pointers are the foundation of advanced C programming including dynamic memory allocation and data structures.

35 min read
Intermediate
Hands-on Examples
What You'll Learn
  • What pointers are and why they matter
  • Address and dereference operators
  • Pointer arithmetic and array access
  • Passing pointers to functions
  • Common pointer pitfalls and debugging
Contents
01

What are Pointers?

A pointer is a variable that stores a memory address instead of a direct value. Think of memory as a giant array of numbered boxes - each box has an address (its position) and can hold data. Regular variables store the data directly, while pointers store the address of where the data lives. This simple concept unlocks incredible power in C programming.

Understanding Memory

Every variable in your program is stored somewhere in computer memory. When you declare int age = 25;, the computer allocates a chunk of memory (4 bytes for an int), stores the value 25 there, and remembers the starting address. Normally, you work with the variable name and let C handle the memory addresses behind the scenes.

However, sometimes you need direct access to those memory addresses. Maybe you want to modify a variable from inside a function, or efficiently pass large amounts of data without copying it. That's where pointers come in - they give you the address so you can work with memory directly.

Key Concept

Pointer

A pointer is a variable whose value is the memory address of another variable. Instead of storing data like 42 or 'A', a pointer stores an address like 0x7ffc5e8c that tells you where to find the actual data in memory.

Pointers are typed - an int* pointer stores the address of an integer, a char* stores the address of a character. This typing tells C how to interpret the data at that address and how many bytes to read.

Key insight: If a variable is a house, a pointer is the house's address written on a piece of paper. The paper itself is small, but it tells you exactly where to find the house.

Declaring Pointers

To declare a pointer, you use the asterisk * symbol after the data type. This asterisk means "pointer to". The pointer type should match the type of variable it will point to - this helps C know how to properly read and write the data at that address.

// Declaring pointers - use * after the type
int *ptr;           // Pointer to an integer
char *charPtr;      // Pointer to a character  
float *floatPtr;    // Pointer to a float
double *doublePtr;  // Pointer to a double

// The * can be placed differently - all are equivalent:
int *ptr1;    // Most common style
int* ptr2;    // Alternative style
int * ptr3;   // Also valid, but less common

This code demonstrates the syntax for declaring pointers to different data types. The asterisk (*) indicates that the variable stores a memory address rather than a direct value.

Warning: When declaring multiple pointers on one line, each needs its own *. Writing int* a, b; creates one pointer a and one regular int b, not two pointers!
// Be careful with multiple declarations!
int* a, b;    // a is a pointer, b is just an int!
int *a, *b;   // Both a and b are pointers - correct way

This code highlights a common mistake when declaring multiple pointers on one line. Each pointer variable needs its own asterisk (*) to be declared as a pointer.

Address 0x1000 0x1004 0x1008 0x100C
Content 42 0x1000 100 0x1008
Variable x ptr (points to x) y ptr2 (points to y)

Memory layout showing variables and pointers. Note how pointers store addresses, not values.

Why typed pointers? An int typically uses 4 bytes while a char uses 1 byte. When you dereference a pointer (read the value it points to), C needs to know how many bytes to read. The pointer type provides this crucial information.

Practice Questions

Task: Declare a pointer for each of the following: an integer named numPtr, a character named letterPtr, and a double named valuePtr.

View Solution
int *numPtr;       // Pointer to int
char *letterPtr;   // Pointer to char
double *valuePtr;  // Pointer to double

Task: What is wrong with this declaration? Fix it.

int* x, y, z;  // Intended: three int pointers
View Solution

Only x is a pointer. y and z are regular integers. The * only applies to the immediately following variable name.

// Fix: Add * before each variable
int *x, *y, *z;  // Now all three are pointers

Task: Write a program that prints the size of different pointer types. Are they the same or different? Why?

View Solution
#include <stdio.h>

int main() {
    printf("Size of int*: %zu bytes\n", sizeof(int*));
    printf("Size of char*: %zu bytes\n", sizeof(char*));
    printf("Size of double*: %zu bytes\n", sizeof(double*));
    printf("Size of void*: %zu bytes\n", sizeof(void*));
    return 0;
}

Output (on 64-bit system):

Size of int*: 8 bytes
Size of char*: 8 bytes
Size of double*: 8 bytes
Size of void*: 8 bytes

All pointers are the same size because they all store memory addresses, which are 8 bytes on a 64-bit system (or 4 bytes on 32-bit). The type only affects how the pointed-to data is interpreted, not the pointer size itself.

02

Pointer Operators

Two operators are fundamental to working with pointers: the address-of operator & which gives you a variable's memory address, and the dereference operator * which lets you access the value at an address. These two operators are opposites - one gets the address, the other follows it to get the value.

The Address-of Operator (&)

The ampersand & placed before a variable returns its memory address. This is how you make a pointer "point to" something - you assign it the address of a variable. Without this operator, a pointer would have no valid address to store.

#include <stdio.h>

int main() {
    int age = 25;
    int *ptr = &age;  // ptr now holds the address of age
    
    printf("Value of age: %d\n", age);        // 25
    printf("Address of age: %p\n", &age);     // 0x7ffd... (some hex address)
    printf("Value stored in ptr: %p\n", ptr); // Same address as above
    
    return 0;
}

This program shows how to use the address-of operator (&) to get a variable's memory address and store it in a pointer. The %p format specifier prints addresses in hexadecimal.

The %p format: Use %p to print pointer values (addresses). It displays the address in hexadecimal format. Always cast to (void*) for portable code: printf("%p", (void*)ptr);

The Dereference Operator (*)

The asterisk * used before a pointer (after declaration) is called the dereference operator. It "follows" the address stored in the pointer and gives you access to the value at that location. You can both read and write values through a dereferenced pointer.

Think of it this way: if a pointer is the address of a house, dereferencing is going to that address and looking inside the house. You can see what's there or change what's inside.

#include <stdio.h>

int main() {
    int score = 100;
    int *ptr = &score;
    
    // Reading through the pointer (dereferencing)
    printf("Value at address %p: %d\n", ptr, *ptr);  // 100
    
    // Writing through the pointer (modifying original variable!)
    *ptr = 200;
    printf("score is now: %d\n", score);  // 200 - changed!
    
    // The pointer and original variable share the same memory
    score = 300;
    printf("*ptr is now: %d\n", *ptr);    // 300 - also changed!
    
    return 0;
}

This program demonstrates the dereference operator (*) which accesses the value at a pointer's address. Changes made through the pointer affect the original variable since they share the same memory location.

Key insight: & and * are opposites. *(&variable) gives you the original variable. &(*ptr) gives you the address stored in ptr.
Operator Name Example Meaning Returns
& Address-of &variable "Give me the address of this variable" Memory address
* Dereference *pointer "Give me the value at this address" Value at the address

Complete Example: Swapping Values

One of the most classic pointer examples is swapping two variables. Without pointers, a swap function cannot modify the original variables because C passes arguments by value. With pointers, the function receives the addresses and can modify the original data.

#include <stdio.h>

// Without pointers - DOES NOT WORK
void badSwap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    // Only swaps local copies, originals unchanged!
}

// With pointers - WORKS CORRECTLY
void swap(int *a, int *b) {
    int temp = *a;  // Get value at address a
    *a = *b;        // Put value at b into location a
    *b = temp;      // Put temp into location b
}

int main() {
    int x = 10, y = 20;
    
    printf("Before: x = %d, y = %d\n", x, y);  // x=10, y=20
    
    badSwap(x, y);
    printf("After badSwap: x = %d, y = %d\n", x, y);  // Still x=10, y=20
    
    swap(&x, &y);  // Pass addresses!
    printf("After swap: x = %d, y = %d\n", x, y);  // x=20, y=10
    
    return 0;
}

This classic example compares pass-by-value vs pass-by-pointer. The badSwap function fails because it only modifies copies, while swap works correctly by using pointers to access and modify the original variables.

Practice Questions

Task: Create a float variable price = 19.99, a pointer to it, then print the value, the address, and the value accessed through the pointer.

View Solution
#include <stdio.h>

int main() {
    float price = 19.99;
    float *ptr = &price;
    
    printf("Value: %.2f\n", price);       // 19.99
    printf("Address: %p\n", (void*)&price);  // 0x...
    printf("Via pointer: %.2f\n", *ptr);  // 19.99
    
    return 0;
}

Task: Create a variable count = 5, then use a pointer to double its value.

View Solution
#include <stdio.h>

int main() {
    int count = 5;
    int *ptr = &count;
    
    printf("Before: %d\n", count);  // 5
    
    *ptr = *ptr * 2;  // Or: *ptr *= 2;
    
    printf("After: %d\n", count);   // 10
    
    return 0;
}

Task: Write a function addToValue(int *num, int amount) that adds amount to the integer pointed to by num. Test it with a variable.

View Solution
#include <stdio.h>

void addToValue(int *num, int amount) {
    *num += amount;  // Dereference and add
}

int main() {
    int balance = 100;
    
    printf("Balance: %d\n", balance);  // 100
    
    addToValue(&balance, 50);
    printf("After deposit: %d\n", balance);  // 150
    
    addToValue(&balance, -30);
    printf("After withdrawal: %d\n", balance);  // 120
    
    return 0;
}
03

Pointers and Arrays

In C, arrays and pointers are deeply connected. An array name is essentially a pointer to its first element, and you can use pointer notation to access array elements. Understanding this relationship is key to mastering both concepts and writing efficient C code.

Array Name as a Pointer

When you declare an array like int numbers[5];, the name numbers represents the memory address of the first element. You can assign this to a pointer without using the & operator because the array name already evaluates to an address.

#include <stdio.h>

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    int *ptr = numbers;  // No & needed! Array name is already an address
    
    // These are all equivalent ways to get the first element:
    printf("%d\n", numbers[0]);  // 10 - array notation
    printf("%d\n", *numbers);    // 10 - pointer dereference
    printf("%d\n", *ptr);        // 10 - pointer variable
    printf("%d\n", ptr[0]);      // 10 - pointer with array notation
    
    return 0;
}

This program shows that an array name acts as a pointer to its first element. All four printf statements access the same value using different but equivalent syntax.

Important distinction: While an array name acts like a pointer, it's not quite the same. An array name is a constant - you cannot reassign it (numbers = ptr; is illegal). A pointer variable can be reassigned to point elsewhere.

Accessing Array Elements with Pointers

Once you have a pointer to an array (or to its first element), you can access any element using pointer arithmetic or array notation. The expression ptr[i] is exactly equivalent to *(ptr + i). C automatically scales the offset by the size of the data type.

#include <stdio.h>

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    int *ptr = numbers;
    
    // Array notation vs pointer arithmetic - both work!
    for (int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d, *(ptr+%d) = %d\n", 
               i, numbers[i], i, *(ptr + i));
    }
    
    // Output:
    // numbers[0] = 10, *(ptr+0) = 10
    // numbers[1] = 20, *(ptr+1) = 20
    // numbers[2] = 30, *(ptr+2) = 30
    // numbers[3] = 40, *(ptr+3) = 40
    // numbers[4] = 50, *(ptr+4) = 50
    
    return 0;
}

This code demonstrates that array indexing (numbers[i]) and pointer arithmetic (*(ptr + i)) are equivalent ways to access array elements. C automatically scales the pointer offset by the element size.

Pointer to Specific Array Element

You can create a pointer to any element in an array, not just the first one. Use the & operator with array indexing to get the address of a specific element.

#include <stdio.h>

int main() {
    int data[5] = {100, 200, 300, 400, 500};
    
    int *first = &data[0];   // Points to first element (same as 'data')
    int *third = &data[2];   // Points to third element (300)
    int *last = &data[4];    // Points to last element (500)
    
    printf("First: %d\n", *first);   // 100
    printf("Third: %d\n", *third);   // 300
    printf("Last: %d\n", *last);     // 500
    
    // You can still use array notation with these pointers
    printf("Element after third: %d\n", third[1]);  // 400
    
    return 0;
}

This example shows how to create pointers to specific array elements using the address-of operator with array indexing. Once you have a pointer to an element, you can use array notation relative to that position.

Array Notation Pointer Notation Meaning
arr[0] *arr or *(arr+0) First element
arr[i] *(arr+i) Element at index i
&arr[i] arr+i Address of element i
arr &arr[0] Address of first element

Practice Questions

Task: Create an array of 4 characters and print each using a pointer and pointer arithmetic.

View Solution
#include <stdio.h>

int main() {
    char letters[4] = {'A', 'B', 'C', 'D'};
    char *ptr = letters;
    
    for (int i = 0; i < 4; i++) {
        printf("%c ", *(ptr + i));  // A B C D
    }
    printf("\n");
    
    return 0;
}

Task: Write a function that takes a pointer to an int array and its size, returns the sum of all elements using pointer arithmetic (not array notation).

View Solution
#include <stdio.h>

int sumArray(int *arr, int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += *(arr + i);  // Pointer arithmetic
    }
    return sum;
}

int main() {
    int nums[] = {1, 2, 3, 4, 5};
    int total = sumArray(nums, 5);
    printf("Sum: %d\n", total);  // 15
    return 0;
}

Task: Write a function that reverses an array in place using two pointers (one starting from the beginning, one from the end).

View Solution
#include <stdio.h>

void reverse(int *arr, int size) {
    int *start = arr;
    int *end = arr + size - 1;
    
    while (start < end) {
        // Swap values at start and end
        int temp = *start;
        *start = *end;
        *end = temp;
        
        start++;  // Move start forward
        end--;    // Move end backward
    }
}

int main() {
    int nums[] = {1, 2, 3, 4, 5};
    int size = 5;
    
    printf("Before: ");
    for (int i = 0; i < size; i++) printf("%d ", nums[i]);
    
    reverse(nums, size);
    
    printf("\nAfter:  ");
    for (int i = 0; i < size; i++) printf("%d ", nums[i]);
    // Output: 5 4 3 2 1
    
    return 0;
}
04

Pointer Arithmetic

Pointer arithmetic lets you move pointers forward or backward in memory. When you add 1 to a pointer, it moves forward by the size of the data type it points to. This is incredibly useful for traversing arrays and working with consecutive memory locations.

Incrementing and Decrementing Pointers

When you increment a pointer (ptr++), it moves to the next element, not the next byte. For an int* on a system where int is 4 bytes, incrementing adds 4 to the address. C handles this scaling automatically based on the pointer type.

#include <stdio.h>

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    int *ptr = numbers;
    
    printf("ptr points to: %d (address: %p)\n", *ptr, (void*)ptr);
    
    ptr++;  // Move to next integer (adds 4 bytes on most systems)
    printf("After ptr++: %d (address: %p)\n", *ptr, (void*)ptr);
    
    ptr += 2;  // Skip 2 integers forward
    printf("After ptr+=2: %d (address: %p)\n", *ptr, (void*)ptr);
    
    ptr--;  // Move back one integer
    printf("After ptr--: %d (address: %p)\n", *ptr, (void*)ptr);
    
    return 0;
}

This program demonstrates pointer increment and decrement operations. Each ++ or -- moves the pointer by the size of the data type (4 bytes for int), not just one byte.

Sample output (addresses will vary):

ptr points to: 10 (address: 0x7ffc1234)
After ptr++: 20 (address: 0x7ffc1238)
After ptr+=2: 40 (address: 0x7ffc1240)
After ptr--: 30 (address: 0x7ffc123c)
Type matters: For char*, adding 1 moves 1 byte. For int*, adding 1 moves 4 bytes. For double*, adding 1 moves 8 bytes. C knows the size from the pointer type.

Pointer Subtraction

You can subtract two pointers of the same type to find out how many elements are between them. This is useful for determining array positions or calculating distances.

#include <stdio.h>

int main() {
    int arr[] = {100, 200, 300, 400, 500};
    int *start = &arr[0];
    int *end = &arr[4];
    
    // Difference gives number of elements between
    long diff = end - start;
    printf("Elements between start and end: %ld\n", diff);  // 4
    
    // Find index of a value
    int *found = &arr[2];  // Pointer to 300
    printf("Index of 300: %ld\n", found - arr);  // 2
    
    return 0;
}

Subtracting two pointers of the same type gives the number of elements between them, not the byte difference. This is useful for calculating array indices or measuring distances in arrays.

Traversing with Pointers

A common pattern is to use a pointer to walk through an array. You can either use a counter with pointer arithmetic, or compare pointers directly to know when you've reached the end.

#include <stdio.h>

int main() {
    int data[] = {1, 2, 3, 4, 5};
    int size = 5;
    
    // Method 1: Counter with pointer increment
    int *ptr = data;
    printf("Method 1: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", *ptr);
        ptr++;
    }
    printf("\n");
    
    // Method 2: Pointer comparison
    int *end = data + size;  // One past the last element
    printf("Method 2: ");
    for (int *p = data; p < end; p++) {
        printf("%d ", *p);
    }
    printf("\n");
    
    return 0;
}

Two common patterns for traversing arrays with pointers: using a counter with pointer increment, or comparing pointers directly to detect the end. Both methods are widely used in C programming.

Allowed operations: You can add/subtract integers to/from pointers, subtract two pointers, and compare pointers. You CANNOT add two pointers, multiply or divide pointers, or perform bitwise operations on pointers.
Operation Example Valid? Result
Add integer ptr + 3 Yes Pointer to 3 elements ahead
Subtract integer ptr - 2 Yes Pointer to 2 elements back
Subtract pointers ptr2 - ptr1 Yes Number of elements between
Compare pointers ptr1 < ptr2 Yes Boolean result
Add pointers ptr1 + ptr2 No Compile error
Multiply pointer ptr * 2 No Compile error

Practice Questions

Task: Use pointer arithmetic to print every other element of an array (indices 0, 2, 4...).

View Solution
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50, 60};
    int size = 6;
    int *end = arr + size;
    
    for (int *p = arr; p < end; p += 2) {
        printf("%d ", *p);  // 10 30 50
    }
    printf("\n");
    
    return 0;
}

Task: Write a function that returns the index of a value in an array, or -1 if not found. Use pointer subtraction to calculate the index.

View Solution
#include <stdio.h>

int findIndex(int *arr, int size, int target) {
    int *end = arr + size;
    for (int *p = arr; p < end; p++) {
        if (*p == target) {
            return p - arr;  // Pointer subtraction gives index
        }
    }
    return -1;  // Not found
}

int main() {
    int nums[] = {5, 10, 15, 20, 25};
    
    printf("Index of 15: %d\n", findIndex(nums, 5, 15));  // 2
    printf("Index of 100: %d\n", findIndex(nums, 5, 100)); // -1
    
    return 0;
}

Task: Write a function to copy one array to another using only pointer operations (no array indexing). The function should use pointer comparison to detect the end.

View Solution
#include <stdio.h>

void copyArray(int *dest, int *src, int size) {
    int *srcEnd = src + size;
    while (src < srcEnd) {
        *dest = *src;
        dest++;
        src++;
    }
    // Or even shorter: while (src < srcEnd) *dest++ = *src++;
}

int main() {
    int original[] = {1, 2, 3, 4, 5};
    int copy[5];
    
    copyArray(copy, original, 5);
    
    printf("Copy: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", copy[i]);  // 1 2 3 4 5
    }
    printf("\n");
    
    return 0;
}
05

Pointers and Functions

Pointers become essential when working with functions. Since C passes arguments by value, a function cannot directly modify its caller's variables unless it receives pointers to them. This is how functions return multiple values or modify data in place.

Pass by Value vs Pass by Pointer

When you pass a regular variable to a function, C creates a copy. Changes to that copy don't affect the original. When you pass a pointer, the function receives the address and can modify the original variable through that address.

#include <stdio.h>

// Pass by value - cannot modify original
void tryToDouble(int x) {
    x = x * 2;
    printf("Inside tryToDouble: x = %d\n", x);
}

// Pass by pointer - CAN modify original
void doubleIt(int *x) {
    *x = *x * 2;
    printf("Inside doubleIt: *x = %d\n", *x);
}

int main() {
    int num = 5;
    
    tryToDouble(num);
    printf("After tryToDouble: num = %d\n\n", num);  // Still 5
    
    doubleIt(&num);
    printf("After doubleIt: num = %d\n", num);  // Now 10
    
    return 0;
}

This comparison shows that pass-by-value only modifies a local copy while pass-by-pointer modifies the original variable. Use pointers when you need a function to change the caller's data.

Returning Multiple Values

A C function can only return one value. But by accepting pointer parameters, a function can effectively "return" multiple values by modifying the pointed-to locations.

#include <stdio.h>

// Calculate both quotient and remainder
void divide(int dividend, int divisor, int *quotient, int *remainder) {
    *quotient = dividend / divisor;
    *remainder = dividend % divisor;
}

int main() {
    int q, r;
    
    divide(17, 5, &q, &r);
    
    printf("17 / 5 = %d remainder %d\n", q, r);  // 3 remainder 2
    
    return 0;
}

Since C functions can only return one value, pointers allow you to "return" multiple values by writing to locations provided by the caller. Here the divide function returns both quotient and remainder.

Arrays as Function Parameters

When you pass an array to a function, C actually passes a pointer to the first element. That's why array parameters can be declared either as arrays or pointers - they're equivalent. This also means the function can modify the original array!

Method 1: Array Notation
void printArray1(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

This function uses traditional array notation with square brackets. The parameter int arr[] indicates we expect an array, but C actually receives a pointer to the first element.

Method 2: Pointer Notation
void printArray2(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", *(arr + i));
    }
    printf("\n");
}

This function uses explicit pointer notation. The parameter int *arr makes it clear we're receiving a pointer. We access elements using pointer arithmetic with *(arr + i).

Modifying Arrays Through Pointers
void doubleArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // Modifies original array!
    }
}

Since arrays are passed as pointers, any changes made inside the function affect the original array. This function doubles each element in place - no return value needed.

Using the Functions
int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    
    printArray1(numbers, 5);  // Output: 1 2 3 4 5
    
    doubleArray(numbers, 5);
    printArray2(numbers, 5);  // Output: 2 4 6 8 10
    
    return 0;
}

When calling these functions, just pass the array name (which decays to a pointer) and the size. After doubleArray modifies the array, printArray2 shows the doubled values.

Why pass size separately? When an array is passed to a function, it "decays" to a pointer and loses size information. sizeof(arr) inside the function gives the pointer size, not the array size. Always pass the size as a separate parameter.

Practice Questions

Task: Write a function increment(int *n) that adds 1 to the value pointed to by n. Test it with a variable.

View Solution
#include <stdio.h>

void increment(int *n) {
    (*n)++;  // Or: *n = *n + 1;
}

int main() {
    int counter = 10;
    printf("Before: %d\n", counter);  // 10
    
    increment(&counter);
    printf("After: %d\n", counter);   // 11
    
    return 0;
}

Task: Write a function that finds both the minimum and maximum values in an array and "returns" them through pointer parameters.

View Solution
#include <stdio.h>

void findMinMax(int *arr, int size, int *min, int *max) {
    *min = arr[0];
    *max = arr[0];
    
    for (int i = 1; i < size; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

int main() {
    int data[] = {3, 7, 1, 9, 4, 6, 2};
    int minimum, maximum;
    
    findMinMax(data, 7, &minimum, &maximum);
    
    printf("Min: %d, Max: %d\n", minimum, maximum);  // Min: 1, Max: 9
    
    return 0;
}

Task: Write a function that takes an array and a pivot value. It should rearrange the array so all elements less than pivot come before elements greater than or equal to pivot. Return the index where the partition occurs through a pointer parameter.

View Solution
#include <stdio.h>

void partition(int *arr, int size, int pivot, int *partitionIndex) {
    int writeIndex = 0;
    
    // Move smaller elements to the front
    for (int i = 0; i < size; i++) {
        if (arr[i] < pivot) {
            // Swap arr[i] and arr[writeIndex]
            int temp = arr[i];
            arr[i] = arr[writeIndex];
            arr[writeIndex] = temp;
            writeIndex++;
        }
    }
    
    *partitionIndex = writeIndex;
}

int main() {
    int data[] = {5, 2, 8, 1, 9, 3, 7};
    int size = 7;
    int partIdx;
    
    printf("Before: ");
    for (int i = 0; i < size; i++) printf("%d ", data[i]);
    
    partition(data, size, 5, &partIdx);
    
    printf("\nAfter (pivot=5): ");
    for (int i = 0; i < size; i++) printf("%d ", data[i]);
    printf("\nPartition at index: %d\n", partIdx);
    
    return 0;
}
06

Common Pointer Pitfalls

Pointers are powerful but can cause subtle and dangerous bugs. Understanding common mistakes will help you write safer code and debug pointer-related issues when they occur. These errors often lead to crashes, data corruption, or security vulnerabilities.

Uninitialized Pointers

A pointer that hasn't been assigned a valid address contains garbage - it could point anywhere in memory. Dereferencing such a pointer leads to undefined behavior, which might crash your program or silently corrupt data.

// DANGEROUS: Uninitialized pointer
int *ptr;        // ptr contains garbage address
*ptr = 42;       // Writing to random memory - undefined behavior!

// SAFE: Initialize to NULL or a valid address
int *ptr1 = NULL;        // Explicitly not pointing to anything
int value = 10;
int *ptr2 = &value;      // Points to a valid variable

Uninitialized pointers contain garbage values and point to random memory locations. Always initialize pointers to NULL or a valid address to prevent undefined behavior and crashes.

Critical: Always initialize pointers. If you don't have a valid address yet, use NULL. This makes it clear the pointer isn't ready to use.

NULL Pointer Dereference

A NULL pointer explicitly points to "nothing". Attempting to dereference it causes a crash (segmentation fault). Always check for NULL before dereferencing, especially for pointers that might come from functions or user input.

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

int main() {
    int *ptr = NULL;
    
    // DANGEROUS: Dereference without checking
    // printf("%d\n", *ptr);  // Crash!
    
    // SAFE: Check before using
    if (ptr != NULL) {
        printf("%d\n", *ptr);
    } else {
        printf("Pointer is NULL - cannot dereference\n");
    }
    
    return 0;
}

Always check if a pointer is NULL before dereferencing it. Dereferencing NULL causes a segmentation fault and crashes your program. This is especially important for pointers returned from functions.

Dangling Pointers

A dangling pointer points to memory that has been freed or a variable that no longer exists (like a local variable after a function returns). Using a dangling pointer is undefined behavior.

#include <stdio.h>

// DANGEROUS: Returning pointer to local variable
int* badFunction() {
    int localVar = 42;
    return &localVar;  // localVar disappears when function returns!
}

int main() {
    int *ptr = badFunction();
    // ptr is now dangling - localVar no longer exists
    // *ptr is undefined behavior!
    
    return 0;
}

A dangling pointer points to memory that no longer exists (like a local variable after its function returns). Never return addresses of local variables - use dynamic allocation or output parameters instead.

Array Out of Bounds

When using pointers with arrays, going past the array bounds reads or writes arbitrary memory. C doesn't check bounds, so these errors silently corrupt data or crash later.

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *ptr = arr;
    
    // Valid access
    printf("%d\n", *(ptr + 2));  // 30 - last element
    
    // DANGEROUS: Out of bounds
    // printf("%d\n", *(ptr + 5));  // Reading garbage!
    // *(ptr + 10) = 100;           // Writing to random memory!
    
    return 0;
}

C does not perform bounds checking on array or pointer access. Accessing memory outside array bounds causes undefined behavior - always track array sizes and validate indices before access.

Type Mismatch

Assigning a pointer of one type to another (without explicit casting) can lead to misinterpreting data. The compiler usually warns about this, but it's still a common source of bugs.

#include <stdio.h>

int main() {
    int value = 1000;
    int *intPtr = &value;
    
    // DANGEROUS: Type mismatch (compiler may warn)
    // char *charPtr = intPtr;  // Wrong! intPtr is int*
    
    // This would read only 1 byte instead of 4
    // char wrongValue = *charPtr;
    
    // If you must cast, be explicit about it
    char *charPtr = (char*)intPtr;  // Explicit cast - you know what you're doing
    
    return 0;
}
Pitfall Cause Consequence Prevention
Uninitialized Pointer Declaring without assigning Random memory access Always initialize (use NULL)
NULL Dereference Using pointer that is NULL Program crash Check != NULL before use
Dangling Pointer Pointing to freed/expired memory Undefined behavior Set to NULL after freeing
Out of Bounds Pointer arithmetic past array Memory corruption Track and check array size
Type Mismatch Wrong pointer type assignment Data misinterpretation Match pointer types

Practice Questions

Task: What is wrong with this code? Fix it.

int *ptr;
*ptr = 100;
printf("%d\n", *ptr);
View Solution

Bug: ptr is uninitialized (contains garbage address). Writing to *ptr writes to random memory.

// Fix: Give ptr a valid address
int value;
int *ptr = &value;  // Now ptr points to valid memory
*ptr = 100;
printf("%d\n", *ptr);  // Safe: prints 100

Task: Write a function safeDouble(int *ptr) that doubles the value pointed to, but only if the pointer is not NULL. Return 1 on success, 0 if NULL.

View Solution
#include <stdio.h>

int safeDouble(int *ptr) {
    if (ptr == NULL) {
        return 0;  // Failed - pointer is NULL
    }
    *ptr *= 2;
    return 1;  // Success
}

int main() {
    int value = 5;
    int *ptr1 = &value;
    int *ptr2 = NULL;
    
    if (safeDouble(ptr1)) {
        printf("Doubled: %d\n", value);  // 10
    }
    
    if (!safeDouble(ptr2)) {
        printf("Could not double - NULL pointer\n");
    }
    
    return 0;
}

Task: This code has a dangling pointer problem. Identify it and explain a safe alternative approach.

int* createAndDouble(int value) {
    int result = value * 2;
    return &result;
}

int main() {
    int *doubled = createAndDouble(21);
    printf("Result: %d\n", *doubled);
    return 0;
}
View Solution

Problem: result is a local variable. When the function returns, result is destroyed, but we return its address. The returned pointer is dangling.

Solution 1: Return the value directly (simplest):

int createAndDouble(int value) {
    return value * 2;
}

int main() {
    int result = createAndDouble(21);
    printf("Result: %d\n", result);  // 42
    return 0;
}

Solution 2: Use an output parameter:

void createAndDouble(int value, int *result) {
    *result = value * 2;
}

int main() {
    int result;
    createAndDouble(21, &result);
    printf("Result: %d\n", result);  // 42
    return 0;
}

Key Takeaways

Pointers Store Addresses

A pointer holds the memory address of another variable, not the value itself. This enables indirect access to data

Two Key Operators

Use & to get an address (address-of) and * to access the value at an address (dereference). They are opposites

Arrays and Pointers Connect

Array names act as pointers to the first element. Use pointer arithmetic or array notation interchangeably

Pointer Arithmetic is Typed

Adding 1 to a pointer moves it by the size of its type. An int* moves 4 bytes, a char* moves 1 byte

Functions Need Pointers

To modify caller variables or return multiple values, pass pointers. Arrays are always passed as pointers

Safety First

Always initialize pointers, check for NULL before dereferencing, and avoid dangling pointers to prevent crashes

Knowledge Check

Quick Quiz

Test what you've learned about C pointers

1 What does the expression &variable return?
2 If int x = 10; int *ptr = &x;, what does *ptr evaluate to?
3 Given int arr[5] = {1, 2, 3, 4, 5};, what is *(arr + 2)?
4 Why do we pass pointers to functions when we want to modify the original variable?
5 What happens if you dereference an uninitialized pointer?
6 If int *p points to an int at address 1000, what address does p + 3 point to (assuming 4-byte int)?
Answer all questions to check your score