
As a C language developer, have you ever faced the problem of needing to implement a generic linked list, only to find that you must write the code separately for each data type? Or designing a callback function but not knowing how to handle various types of parameters?
The root of these issues lies in the fact that C is a strongly typed system.
C requires every variable, function parameter, and return value to have a defined type, which ensures program safety but also limits flexibility.
Thus, the core contradiction here is: how to maintain type safety while achieving sufficient programming flexibility.
This is the significance of the <span>void*</span> pointer — it is the universal type in C.
The Cornerstone of Generic Programming
In C, which lacks templates and generic mechanisms, <span>void*</span> is the key tool for achieving “write once, use everywhere”.
Take the <span>qsort()</span> function from the standard library as an example:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
This function can sort arrays of any type, whether integers, floating-point numbers, or custom structures, and the secret lies in the <span>void*</span> parameter:
<span>base</span>parameter accepts the address of the first element of any type of array<span>compar</span>comparison function accepts any type of element through<span>void*</span>
It can be used like this:
// Integer comparison function
int compare_ints(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// Using qsort to sort an integer array
int arr[] = {5, 2, 8, 1, 3};
qsort(arr, 5, sizeof(int), compare_ints);
With <span>void*</span>, a sorting function can handle all data types, which is the essence of “generic programming” in C.
Abstract Tool for Memory Operations
In low-level system programming, we often need to manipulate memory blocks without caring about the specific data types stored within, and <span>void*</span> was created for this purpose.
The most typical example is the memory allocation function:
void* malloc(size_t size);
<span>malloc()</span> returns <span>void*</span> because it does not care what type of data you will store in this memory block; malloc only allocates a specified size of memory.
This design allows the same function to allocate memory for any data type.
Similarly, memory operation functions like <span>memcpy()</span> and <span>memset()</span> also use <span>void*</span>:
void* memcpy(void *dest, const void *src, size_t n);
void* memset(void *s, int c, size_t n);
These functions treat memory as a pure sequence of bytes, ignoring type information, thus enabling generic operations on any data type.
The Universal Glue for Interface Design
In modular programming, <span>void*</span> is the ideal tool for connecting different modules, especially when designing callback functions and generic interfaces.
For example, in thread creation, the POSIX thread library’s <span>pthread_create()</span> function:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Here, the <span>void *arg</span> parameter allows you to pass any type of data to the thread function without having to create different versions of the function for each possible parameter type.
void* thread_function(void *arg) {
struct thread_data *my_data = (struct thread_data*) arg;
// Use my_data...
return NULL;
}
// Create a thread and pass a custom data structure
struct thread_data data = {/* ... */};
pthread_create(&thread, NULL, thread_function, &data);
This design pattern is widely used in event handling, plugin systems, and callback mechanisms, making interface design more flexible and generic.
What is the Cost?
<span>void*</span>’s flexibility comes at the cost of sacrificing type safety, which can lead to serious issues, primarily two:
Type Misuse Leading to Memory Crashes: Incorrectly interpreting the data type pointed to by a pointer can lead to memory access violations, alignment errors, or data corruption.
// Dangerous example
void* data = malloc(sizeof(int));
*(double*)data = 3.14; // Type mismatch, may lead to memory overflow
Decreased Readability: Overusing <span>void*</span> can turn code into a “black box”, making it difficult to understand and maintain.
void process_data(void* data, int type) {
// Determine the actual type of data based on type
switch(type) {
case 1: /* Process integer */ break;
case 2: /* Process float */ break;
// ...
}
}
This kind of code is hard to trace the data type, making it easy to introduce errors.
The Philosophy of C Language
<span>void*</span> reflects the design philosophy of C language well, which has been mentioned multiple times before, that is, C assumes that programmers know what they are doing and gives them complete control.
In today’s evolving programming languages, many modern languages offer safer alternatives through generics, interfaces, and dynamic types, but <span>void*</span>, as a classic design of C, still plays an irreplaceable role in the underlying code of countless systems.
Material sourced from “Survival on a Programmer’s Deserted Island”, the work belongs to the original author and is for learning reference only~
Finally
The author has collected some embedded learning materials, reply in the public account with 【1024】 to find the download link!
Recommended good articles Click the blue text to jump
☞ Collection | Comprehensive Programming of Linux Applications
☞ Collection | Learn Some Network Knowledge
☞ Collection | Handwritten C Language
☞ Collection | Handwritten C++ Language
☞ Collection | Experience Sharing
☞ Collection | From Microcontrollers to Linux
☞ Collection | Power Control Technology
☞ Collection | Essential Mathematics for Embedded Systems
☞ MCU Advanced Collection