(void *)
The void pointer is one of C's most powerful and dangerous constructs. Learn how this generic pointer enables flexible memory handling, generic data structures, and low-level system programming.
Introduction
The void * pointer is one of the most powerful and dangerous constructs in C programming.
It represents a pointer to "nothing" - or more precisely, a pointer to an unspecified type. Understanding
void pointers is essential for low-level programming, generic data structures, and interfacing with system APIs.
What is void *?
A void pointer is a generic pointer that can hold the address of any data type. Unlike typed pointers, it doesn't carry type information, making it both flexible and risky:
void *ptr;
int x = 42;
float y = 3.14f;
char c = 'A';
ptr = &x; // Valid - points to int
ptr = &y; // Valid - points to float
ptr = &c; // Valid - points to char
Why Use void Pointers?
Void pointers enable several important patterns in C:
- Generic functions - Functions like
memcpy()andqsort()work with any type - Dynamic memory -
malloc()returnsvoid *for type-agnostic allocation - Callback data - Pass arbitrary user data to callback functions
- Opaque handles - Hide implementation details behind abstract pointers
The malloc() Connection
The most common encounter with void pointers is through dynamic memory allocation:
#include <stdlib.h>
// malloc returns void * - automatically converts to any pointer type
int *arr = malloc(10 * sizeof(int));
char *str = malloc(256);
struct Node *node = malloc(sizeof(struct Node));
// In C++, explicit cast is required
int *arr = (int *)malloc(10 * sizeof(int));
Dereferencing Rules
You cannot directly dereference a void pointer - it must be cast to a concrete type first:
void *ptr = malloc(sizeof(int));
// ERROR: cannot dereference void pointer
// *ptr = 42;
// Correct: cast first, then dereference
*(int *)ptr = 42;
int value = *(int *)ptr;
// Or use an intermediate pointer
int *int_ptr = (int *)ptr;
*int_ptr = 42;
Pointer Arithmetic
Standard C prohibits pointer arithmetic on void pointers since the element size is unknown. However, GCC allows it as an extension, treating void * like char *:
void *ptr = some_buffer;
// Non-standard (GCC extension)
ptr++; // Advances by 1 byte
// Portable approach
ptr = (char *)ptr + 1; // Explicitly cast to char *
// Or cast to the actual type
int *int_ptr = (int *)ptr;
int_ptr++; // Advances by sizeof(int)
Building Generic Data Structures
Void pointers enable generic containers in C:
typedef struct {
void **items;
size_t size;
size_t capacity;
} Vector;
void vector_push(Vector *v, void *item) {
if (v->size >= v->capacity) {
v->capacity = v->capacity ? v->capacity * 2 : 8;
v->items = realloc(v->items, v->capacity * sizeof(void *));
}
v->items[v->size++] = item;
}
void *vector_get(Vector *v, size_t index) {
return index < v->size ? v->items[index] : NULL;
}
The qsort() Pattern
The standard library's qsort() demonstrates idiomatic void pointer usage:
#include <stdlib.h>
int compare_ints(const void *a, const void *b) {
int arg1 = *(const int *)a;
int arg2 = *(const int *)b;
return (arg1 > arg2) - (arg1 < arg2);
}
int main() {
int arr[] = {5, 2, 8, 1, 9};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare_ints);
// arr is now {1, 2, 5, 8, 9}
}
Type Safety Concerns
The flexibility of void pointers comes at the cost of type safety. The compiler cannot catch type mismatches:
void process(void *data, int type) {
switch (type) {
case TYPE_INT:
printf("%d\n", *(int *)data);
break;
case TYPE_FLOAT:
printf("%f\n", *(float *)data);
break;
}
}
// Bug: passing wrong type - compiles fine, crashes at runtime
float f = 3.14;
process(&f, TYPE_INT); // Undefined behavior!
Modern Alternatives
In newer code, consider these alternatives to raw void pointers:
- C11 _Generic - Type-generic macros for compile-time dispatch
- Tagged unions - Explicit type tracking with discriminated unions
- Code generation - Macros or external tools to generate typed versions
- C++ - Templates provide type-safe generics
- Rust - Generics and trait objects with full type safety
Conclusion
The void pointer is a fundamental tool in C's type system that enables generic programming
at the cost of type safety. While modern languages offer safer alternatives, understanding
void * is essential for systems programming, interfacing with C libraries,
and appreciating why newer languages made different design choices.