Hello! Welcome to the first lesson in our module on "Advanced Pointer Applications and Performance."
In the previous module, we focused heavily on using type qualifiers like const to add constraints and build safer, more explicit contracts into our code. We saw how the type system is your ally in preventing errors. Now, we're going to explore a technique that does the exact opposite: it intentionally discards type information to achieve flexibility.
Today's learning outcome is to use void pointers for generic programming and understand the associated type safety implications. This is the fundamental mechanism in C for creating functions and data structures that can work with any data type. For someone with your background in complex middleware like AUTOSAR, you've likely seen APIs that rely on this technique for things like generic message queues, event handlers, or communication buffers. Today, we'll dissect how and why it works, and more importantly, where the dangers lie.
The "What": A Pointer to Anything
A regular pointer, like an int* or a char*, is typed. It "knows" what kind of data it points to. This allows the compiler to perform two key actions:
- Type Checking: It ensures you don't accidentally assign the address of a
floatto anint*. - Pointer Arithmetic: When you increment an
int*, it moves forward bysizeof(int)bytes.
A void pointer, or void*, is a generic pointer. It can hold the address of any type of variable, but it gives up the benefits of a typed pointer.
- It can point to anything: You can assign the address of any variable to a
void*without a cast. - It has no type information: The compiler doesn't know the size or layout of the data it points to.

This lack of type information leads to two critical limitations:
- You cannot dereference a
void*directly. Ifptris avoid*,*ptris illegal. The compiler has no idea how many bytes to read from memory or how to interpret them. - You cannot perform pointer arithmetic on a
void*(in standard C). An expression likeptr++is not allowed because the compiler doesn't know the size of the object to step over. (Note: The GNU C compiler treatssizeof(void)as 1, soptr++would advance by one byte, but relying on this is not portable).
The "Why": Enabling Generic Programming
So, if void pointers are so limited, why use them? They are the key to writing generic functions in C.
Imagine you need to write a function to print the value of a variable. Without void pointers, you'd need to write a separate function for each data type.
C++ POINTERS (2025) - What is a void pointer? (for beginners) PROGRAMMING TUTORIAL
To understand the motivation for void pointers, let's watch a segment from the video 'C++ POINTERS (2025)' by CodeBeauty. It clearly demonstrates the problem of code duplication and how void pointers offer a solution, even though the video title mentions C++ the content is pure C.
Watch from 0:33 to 5:15. The first part introduces void pointers, and the second part illustrates the problem by creating separate print_number and print_letter functions. This sets the stage for a generic solution.
The solution is to create a single, generic function that accepts a void*. However, since the function has no type information, we must provide it ourselves.
The "How": Casting and Programmer Responsibility
The fundamental pattern for using void pointers is:
- A function accepts a
void*to be generic. - The caller provides the function with the necessary information to interpret the data (e.g., a type tag, the size of the data, or a function pointer for operations).
- Inside the function, you cast the
void*back to a specific, typed pointer. - You can then dereference and operate on the newly typed pointer.
This act of casting is you, the programmer, telling the compiler, "Trust me, I know that this void* actually points to an integer." The compiler will obey, but the responsibility for correctness is now entirely on you.
This next video segment demonstrates this process by building a generic print function.
C++ POINTERS (2025) - What is a void pointer? (for beginners) PROGRAMMING TUTORIAL
Let's continue with the CodeBeauty video. This part shows how to implement the generic function using a void* and an extra type parameter to manage the data type. Pay close attention to the switch statement and the casting.
Watch from 5:15 to 12:31. This section covers the implementation of the generic print function. Notice how it uses a character ('i', 'c') to signal the type, and then casts the void* to int* or char* before dereferencing.
The Catch: Type Safety Implications
What happens if you lie to the compiler? What if you pass the address of an int but tell your generic function it's a char? The compiler won't stop you. This is the critical trade-off of void pointers: you gain flexibility at the cost of compile-time safety.
This leads to logical errors that can be incredibly difficult to debug, as they often manifest as corrupted data or strange behavior far from the source of the bug. Given your work with static analysis tools like Coverity and QAC, you know that these types of errors are precisely what the tools are designed to catch, but they can only do so if the rules aren't being deliberately subverted.
This final clip from the video powerfully illustrates the danger.
C++ POINTERS (2025) - What is a void pointer? (for beginners) PROGRAMMING TUTORIAL
This last segment is the most important for our learning outcome. It directly addresses the type safety implications by showing what happens when you cast a void* to the wrong type.
Watch from 12:31 to 15:02. The presenter demonstrates passing an int pointer but accidentally casting it to a char*. The program compiles without error but produces garbage output. This is the essence of the risk associated with void pointers.
A Real-World Example: qsort and Function Pointers
While passing a type tag is a simple approach, a more powerful and common pattern for generic programming in C is to pass function pointers that encapsulate the type-specific logic. The standard library's qsort function is the classic example.
The signature is:void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *));
base: Avoid*to the start of the array to be sorted.nitems: The number of elements in the array.size: The size of each element (e.g.,sizeof(int)).qsortuses this for its internal pointer arithmetic.compar: A pointer to a comparison function that you must provide. This function knows how to compare two elements.
Your comparison function must have the signature int compar(const void *a, const void *b). Inside this function, you are responsible for casting a and b to the correct pointer type before comparing them.
CS107 Lab 4: void * and Function Pointers
The 'CS107 Lab 4' guide from Stanford provides an excellent explanation of this pattern. We'll focus on its introduction to comparison functions.
Read the section titled 'Review - Comparison Functions'. It lays out the three-step pattern for implementing a comparison function: 1. Cast the void*, 2. Dereference the typed pointer, 3. Compare the values. It also states a critical principle: generic functions work with pointers to values, never the values directly.
Let's see this in practice by watching a developer convert a type-specific sorting function into a generic one using void* and a function pointer, just like qsort.
C Tutorial: void *, generic data-structures, and functions
This video from Gabriel Parmer shows the live-coding process of making a data structure generic. It's a bit more advanced and demonstrates the real-world thought process, including the messy syntax.
Watch the following three clips. First, from 2:07 to 5:03, he introduces qsort as the motivating C API. Next, from 43:21 to 54:01, watch as he modifies an integer-specific sorter to use void** for storing generic data. Finally, from 54:01 to 58:44, he implements and passes the comparison function pointer, tying it all together. Don't worry about catching every detail; focus on the overall pattern of abstracting the type away.
Test your understanding!
You need to write a generic swap function that can swap any two variables of the same type. Its signature will be void generic_swap(void *a, void *b, size_t size);.
Given what you've learned about void* and the need to manage memory operations manually, how would you implement this function? You'll likely need a standard library function for moving raw bytes.
Show answer
To implement generic_swap, you cannot simply dereference a and b. You need to perform a byte-wise swap using a temporary buffer. The standard memcpy function is perfect for this.
#include <stdlib.h> // for malloc/free
#include <string.h> // for memcpy
void generic_swap(void *a, void *b, size_t size) {
// Cannot swap if size is 0.
if (size == 0) return;
// Allocate a temporary buffer on the heap of the correct size.
void *temp = malloc(size);
if (temp == NULL) {
// Handle allocation failure, maybe log an error.
return;
}
// Perform the three-way swap using memcpy
memcpy(temp, a, size); // temp = a
memcpy(a, b, size); // a = b
memcpy(b, temp, size); // b = temp
// Free the temporary buffer
free(temp);
}
// Example Usage:
// int x = 10, y = 20;
// generic_swap(&x, &y, sizeof(int));
//
// double d1 = 3.14, d2 = 2.71;
// generic_swap(&d1, &d2, sizeof(double));
This implementation correctly handles data of any size by treating it as a raw block of memory, which is the essence of programming with void pointers. Note: A stack-allocated buffer (char temp[size];) using a variable-length array (VLA) could also work for small size, but heap allocation is more general and safer for larger types.
Performance and Aliasing
For your work in communication stacks and performance-critical embedded systems, it's important to know that void* is not a "zero-cost" abstraction.
- Function Call Overhead: As we saw with
qsort, generic functions often rely on function pointers (compar). This function call has more overhead than an inlined comparison (e.g.,a < b). - Strict Aliasing Rule: This is a more subtle but critical point. Because a
void*can "alias" (point to the same memory as) any other pointer, the compiler must be extremely conservative with optimizations. It cannot assume that a write through anint*doesn't affect the data pointed to by avoid*. This can prevent optimizations and force the CPU to re-read values from memory more often than necessary. This is precisely the problem that therestrictkeyword (which we covered previously) is designed to solve.
Using void* in Generic C Programming may be Inefficient
This article, though brief, highlights the potential inefficiency of using void*. We'll focus on the sections discussing aliasing.
First, read the main text in the first two paragraphs, which introduces the overheads. Then, jump to the comment by 'Michiel' (comment #4). It perfectly explains why aliasing with void* hurts compiler optimizations. This connects directly to our previous discussions on restrict.
Conclusion
Today we've pulled back the curtain on one of C's most powerful and perilous features. By trading compile-time safety for runtime flexibility, void pointers allow us to write generic, reusable code, but they place the burden of correctness squarely on our shoulders.
Key Takeaways:
- A
void*is a generic pointer that can hold the address of any data type. - It cannot be dereferenced or used in pointer arithmetic directly. You must first cast it to a typed pointer.
- Generic programming with
void*requires passing extra information (like data size, type tags, or function pointers) to guide the generic function. - The greatest risk is a type safety implication: casting a
void*to the wrong pointer type is a logical error that compilers won't catch, leading to Undefined Behavior or difficult-to-debug data corruption. - This technique is not without performance costs, primarily due to function call overhead and compiler limitations related to pointer aliasing.
Preview of the Next Lesson:
We've just seen how void* can be used to create generic APIs that work with any data type. In our next lesson, we will look at another advanced pointer technique for API design with the opposite goal: information hiding. We will learn how to implement opaque pointers (the 'pimpl' idiom) to completely hide a data structure's implementation details from the API's users, creating stable and encapsulated interfaces.