Void pointers and generic programming
Use void * to write type-erased containers and generic algorithms in C — the technique behind qsort, malloc, and generic data structures.
- Explain what void * means and how to convert to and from it
- Write a generic swap function using void * and element size
- Implement a simple generic container using void * pointers
- Identify the safety trade-offs of type erasure in C
void * is a pointer to memory of unknown type. It is the foundation of generic programming in C — how malloc returns memory that can hold any type, how qsort sorts arrays of any element type, and how you build containers that work with multiple types without duplicating code.
void * basics
void *ptr;
int x = 42;
double d = 3.14;
ptr = &x; /* any pointer implicitly converts to void * */
ptr = &d; /* no cast required in C (unlike C++) */You cannot dereference void * directly — the compiler does not know the type, so it does not know how many bytes to read. You must cast to a concrete pointer type first:
ptr = &x;
int value = *(int *)ptr; /* cast to int * then dereference */malloc returns void *
malloc's return type is void *:
void *malloc(size_t size);In C, the void * is implicitly converted to any pointer type on assignment — no cast needed:
int *arr = malloc(10 * sizeof(int)); /* implicit conversion: void * → int * */This is why malloc can return memory for any type without a template system.
A generic swap
Using void * and element size, you can swap any two values:
#include <string.h>
void generic_swap(void *a, void *b, size_t size) {
char tmp[size]; /* VLA (C99): stack buffer of exactly the right size */
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
}
int main(void) {
int x = 1, y = 2;
generic_swap(&x, &y, sizeof(int));
printf("x=%d y=%d\n", x, y); /* x=2 y=1 */
double p = 3.14, q = 2.71;
generic_swap(&p, &q, sizeof(double));
printf("p=%.2f q=%.2f\n", p, q); /* p=2.71 q=3.14 */
return 0;
}memcpy copies raw bytes — it does not care about the type. This is exactly what qsort does internally.
A generic stack
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
void *data; /* raw byte buffer */
size_t elem_size;
int len;
int capacity;
} GenStack;
void gs_init(GenStack *s, size_t elem_size) {
s->data = NULL;
s->elem_size = elem_size;
s->len = 0;
s->capacity = 0;
}
int gs_push(GenStack *s, const void *elem) {
if (s->len == s->capacity) {
int new_cap = (s->capacity == 0) ? 8 : s->capacity * 2;
void *tmp = realloc(s->data, new_cap * s->elem_size);
if (!tmp) { return -1; }
s->data = tmp;
s->capacity = new_cap;
}
/* copy element bytes into slot */
memcpy((char *)s->data + s->len * s->elem_size, elem, s->elem_size);
s->len++;
return 0;
}
int gs_pop(GenStack *s, void *out) {
if (s->len == 0) { return -1; }
s->len--;
memcpy(out, (char *)s->data + s->len * s->elem_size, s->elem_size);
return 0;
}
void gs_free(GenStack *s) {
free(s->data);
s->data = NULL;
s->len = s->capacity = 0;
}
int main(void) {
GenStack stack;
gs_init(&stack, sizeof(int));
for (int i = 1; i <= 5; i++) {
gs_push(&stack, &i);
}
int val;
while (gs_pop(&stack, &val) == 0) {
printf("%d ", val);
}
printf("\n"); /* 5 4 3 2 1 */
gs_free(&stack);
return 0;
}(char *)s->data + index * elem_size computes the byte offset of element index. Casting to char * is necessary because you cannot do arithmetic on void * (the size of the pointee is unknown).
Safety trade-offs
void * erases type information. The compiler cannot catch:
GenStack int_stack;
gs_init(&int_stack, sizeof(int));
double d = 3.14;
gs_push(&int_stack, &d); /* compiles -- pushing a double into an int stack */
int val;
gs_pop(&int_stack, &val); /* reads the double bytes as an int -- undefined */In C, type safety for generic containers is the programmer's responsibility. C++ templates, Rust generics, and Go interfaces all provide safer alternatives, but they are built on top of the same underlying mechanisms C exposes directly.
Where to go next
Next: memory alignment and padding — why the compiler inserts padding between struct members, how to control it, and why alignment matters for performance and correctness.