Skip to Content
Course content

50: Void Pointers and Generic Code

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small project recently where I needed to swap two values. At first, it seemed trivial. I wrote a quick swap function for integers, and it worked perfectly. But then I realized I needed to swap doubles, then a custom struct User, and then a few char arrays. I found myself writing the same logic over and over, just changing the type declaration.

The Repetitive Swap Problem

Here is what my code looked like. It's boring, repetitive, and frankly, a maintenance nightmare:

void swap_int(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void swap_double(double *a, double *b) {
    double temp = *a;
    *a = *b;
    *b = temp;
}

I thought, "There has to be a way to tell C: 'I don't care what the type is, just swap the memory at these two addresses.'" I remember seeing void * in the documentation for malloc and qsort, so I decided to try it out.

Trying the "Any Pointer" Approach

My first instinct was to just replace the specific types with void *. I figured the compiler would just "figure it out."

void generic_swap(void *a, void *b) {
    void *temp = *a; // Error here
    *a = *b;         // Error here
    *b = temp;       // Error here
}

The compiler immediately yelled at me. The error was essentially: "Invalid use of void expression."

This is where the reality of void * hits. A void pointer is a "generic" pointer; it holds a memory address, but it has no associated type. Because it has no type, the compiler has no idea how many bytes it's looking at. Is it a 4-byte integer? An 8-byte double? A 200-byte struct? Without knowing the size, the compiler refuses to let me dereference it (the *a part). It can't "fetch" the value because it doesn't know where the value ends.

Treating Memory as a Bag of Bytes

So, if the compiler doesn't know the size, I have to tell it. I realized that every data type in C, regardless of what it is, is ultimately just a sequence of bytes. If I can treat the pointers as char * (since a char is exactly one byte), I can move the data one byte at a time.

I tried this approach: I'll pass the size of the element as an argument, cast the void pointers to char *, and use a temporary buffer to shuffle the bytes around.

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

void generic_swap(void *a, void *b, size_t size) {
    // I need a temporary place to hold 'size' bytes
    void *temp = malloc(size);
    
    // memcpy doesn't care about types, only addresses and sizes
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
    
    free(temp);
}

Now it works! I can pass an int, a double, or a struct, and as long as I provide the correct sizeof(...), the bytes are swapped correctly. I'm essentially bypassing the C type system to manipulate raw memory.

The Trade-off of Genericity

This feels powerful, but I noticed something immediately. When I used swap_int, if I accidentally passed a float *, the compiler would warn me. With generic_swap, the compiler is silent. If I pass a pointer to an integer but tell the function the size is 100 bytes, I'll end up reading into memory I don't own and likely crashing the program with a segmentation fault.

That's the deal you make with void *: you gain the ability to write code that works for any type, but you lose the safety net of the compiler's type-checking. You are now the one responsible for ensuring the sizes and types match up.




📋 Practical Task

Build a Generic Memory-Copying Buffer Reverser

Your task is to create a function called reverse_array_generic. This function should be able to reverse the elements of an array of any data type.

The function signature must be: void reverse_array_generic(void *base, size_t num_elements, size_t element_size)

  • base: A pointer to the start of the array.
  • num_elements: How many elements are in the array.
  • element_size: The size of a single element (use sizeof when calling the function).

To implement this, you will need to:

  1. Use a loop to swap the first element with the last, the second with the second-to-last, and so on.
  2. Perform the actual swap using memcpy and a temporary buffer (similar to the generic_swap logic shown in the lesson).
  3. Calculate the memory offset for each element manually using char * arithmetic, since you cannot perform pointer arithmetic on void *.

Test your function with an array of ints and an array of doubles to ensure it handles both correctly before submitting.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.