C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
50: Void Pointers and Generic Code
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 (usesizeofwhen calling the function).
To implement this, you will need to:
- Use a loop to swap the first element with the last, the second with the second-to-last, and so on.
- Perform the actual swap using
memcpyand a temporary buffer (similar to thegeneric_swaplogic shown in the lesson). - Calculate the memory offset for each element manually using
char *arithmetic, since you cannot perform pointer arithmetic onvoid *.
Test your function with an array of ints and an array of doubles to ensure it handles both correctly before submitting.
There are no comments for now.