Void Pointers in C
In C language, defining a pointer means also specifying the type of data that the pointer points to.
However, there exists another type of pointer, called void pointer, that can point to any memory area, regardless of the data type that is stored there. In this lesson we will see what void pointers are and how to use them in C language.
void Pointers
In previous lessons on pointer variables we saw that when we declare a pointer in C language, we must specify the type of data that the pointer points to. For example, if we declare a pointer to integer, the type of data that the pointer points to is int.
int *p;
Declaring the type that a pointer points to is fundamental for the compiler. In fact, knowing this information the compiler is able to correctly calculate the memory offset to access the pointed data and to perform the dereferencing of the pointer.
We said, in fact, that the address of any variable in memory corresponds to the address of its first byte.
So, if we have a pointer to double, the compiler knows that to access the pointed data it must read, starting from the pointed address, 8 bytes of memory. In fact a double occupies 8 bytes.
double *p;
There are cases, however, in which programs that directly access the hardware or the computer's memory must store or pass addresses of memory locations without knowing what types of data are actually stored there.
For this reason, in C language there exists a special type of pointer, called void pointer.
A void pointer, often called pointer to void, is a generic pointer that can be defined with the following syntax:
void *p;
This pointer can point to any memory area, regardless of the data type that is stored there.
For example, if we want to use a void pointer to store the address of a variable of type int, we can do the following:
int a = 10;
void *p = &a;
In this case, the pointer p points to the memory address of a, but the compiler does not know what type of data is stored in that memory location.
To access the data pointed to by a void pointer, we must perform an explicit casting of the pointer to the correct data type.
int b = *((int *)p);
In the example above we performed a casting of the pointer p to type int * and then we dereferenced the pointer to get the value of a.
void * Pointer
In C language, a void pointer is a generic pointer that can point to any memory area, regardless of the data type that is stored there.
The syntax to declare a void pointer is the following:
void *p;
To access the data pointed to by a void pointer, we must perform an explicit casting of the pointer to the correct data type:
type b = *((type *)p);
When we dereference a void pointer, however, we must take the utmost care.
What happens, in fact, when we perform an explicit cast to a wrong data type?
int a = 10;
void *p = &a;
/* What happens in this case? */
double b = *((double *)p);
In this case, we are performing a casting of the pointer p to type double *, but the data pointed to by p is of type int. While a double occupies 8 bytes, an int might occupy only 4. So, with the dereferencing operation, we could access the next 4 bytes.
In that case, the program's behavior becomes undefined. The program could work correctly, it could generate an error at runtime or it could return an incorrect value.
Attention to casting void pointers
When we dereference a void pointer, we must be careful to perform an explicit casting to the correct data type. If we perform a casting to a wrong data type, the program's behavior becomes undefined.
Possible Operations with void Pointers
void pointers are in all respects pointers. Therefore it is possible to perform the same operations as normal pointers on them except for some operations.
Let's see what the allowed operations are:
-
Address assignment:
To a
voidpointer we can assign the memory address of a variable of any type.int a = 10; void *p = &a; -
Dereferencing:
We can dereference a
voidpointer as long as we perform an explicit casting of the pointer to the correct data type.int b = *((int *)p); -
Assignment of a pointer to another pointer:
We can assign a
voidpointer to anothervoidpointer.void *q = p; -
Comparison between pointers:
We can compare two
voidpointers with each other.if (p == q) { // ... } else { // ... }
In this they behave like normal pointers.
However, since the compiler does not know the type of data that a void pointer points to, we cannot perform some operations that require this information. The operations of pointer arithmetic, in fact, are not allowed on void pointers:
-
Addition or subtraction of an integer is not allowed:
We cannot perform the addition or subtraction of an integer to a
voidpointer./* ERROR: Addition of an integer to a * void pointer is not allowed */ p = p + 1;/* ERROR: Subtraction of an integer from a * void pointer is not allowed */ p = p - 1; -
Increment or decrement of a pointer is not allowed:
We cannot increment or decrement a
voidpointer./* ERROR: Increment of a void pointer * is not allowed */ p++;/* ERROR: Decrement of a void pointer * is not allowed */ p--; -
Subtraction of two
voidpointers is not allowed:We cannot subtract two
voidpointers./* ERROR: Subtraction of two void pointers * is not allowed */ int diff = p - q;
Summarizing:
Allowed Operations with void Pointers
On void pointers all operations allowed on normal pointers are allowed, except for pointer arithmetic operations.
The allowed operations on void pointers are:
- Address assignment
- Dereferencing
- Assignment of a pointer to another pointer
- Comparison between pointers
Although these rules exist, some compilers, like GCC, allow performing some pointer arithmetic operations on void pointers. In fact, they consider void pointers as pointers to a byte. Therefore, when for example a void pointer is incremented, the pointed address is incremented by one byte.
void *p = (void *)0x1000;
p++;
In this case, the pointer p points to address 0x1001 if we use a compiler that allows this operation.
Our advice, however, is to avoid performing pointer arithmetic operations on void pointers to avoid undefined behaviors.
Furthermore, GCC can be configured to emit a warning or an error when pointer arithmetic operations are performed on void pointers. Just use the -Wpointer-arith option to enable the warning or -Werror=pointer-arith to turn the warning into an error.
gcc -Wpointer-arith -o program program.c
gcc -Werror=pointer-arith -o program program.c
When to Use void Pointers
At this point the question arises: given the dangers and limitations in using void pointers, when does it make sense to use them?
There are mainly three cases in which void pointers are used.
The first case concerns direct interfacing with hardware. When writing drivers for hardware devices, one works directly with memory areas, especially nowadays, since many devices are memory mapped.
For example, a graphics card could map, that is associate, a memory area to which to write pixels directly in memory. A driver or program could access this memory using void pointers and, subsequently, perform the explicit casting to the correct data type.
A second use concerns instead functions that cannot make assumptions about the data type of one or more parameters.
A simple example could be that of a function that must perform the sum of two values, but does not know in advance if the values are integers, floating point or of another type. Then, the function could accept two void pointers, the addends, a void pointer for the result and a fourth parameter that indicates the data type of the addends:
void sum(void *a, void *b, void *result, int type) {
switch (type) {
case 0:
/* Sum between Integers */
*((int *)result) = *((int *)a) + *((int *)b);
break;
case 1:
/* Sum between Doubles */
*((double *)result) = *((double *)a) + *((double *)b);
break;
default:
break;
}
}
We can invoke this function in this way:
int a = 10;
int b = 20;
int result;
sum(&a, &b, &result, 0);
In the next lessons we will see more sophisticated uses of functions that accept void pointers.
A last typical use of void pointers concerns dynamic memory allocation. This is a fundamental topic that we will see in the next lessons. For now it is enough to know that in C it is possible to dynamically allocate memory using the malloc function and that this function returns a void pointer. In fact, this function accepts as input not a type but the size in bytes of the memory to be allocated.
void *p = malloc(number_of_bytes);
In this case, p is a void pointer that points to the dynamically allocated memory area. We postpone, however, the details on dynamic memory allocation to the next lessons.
There are certainly other use cases but these are the three typical cases. In any case, in high-level code it is always better to avoid using void pointers and one should look with suspicion at code that makes excessive use of these pointers because it could represent a design error of the code.
In Summary
In this lesson we saw void pointers in C language. We saw that void pointers are generic pointers that can point to any memory area, regardless of the data type that is stored there.
We saw that to access the data pointed to by a void pointer we must perform an explicit casting of the pointer to the correct data type.
Finally, we saw that void pointers are in all respects pointers, but that we cannot perform some pointer arithmetic operations on them.