Each ordinary pointer has a fixed type. An int* can point to the address of an int only; a float* can point to the address of a float only. That rule guards the program against many errors; but as a consequence, it also ensures a single pointer variable can never point at objects of different types.
Void pointers are present for those rare instances where you actually need to remove the restriction. It is declared by writing void as the pointed to type:
void* ptr {};
The variable ptr is still a pointer and still holds an address. However, now the compiler simply doesn't remember what kind of object is at that address. Since the type information was discarded, the same void pointer can once again hold the address of an int, float, character array, or just about anything else.
#include <iostream>
int main()
{
int nValue { 5 };
// The f suffix makes this a float literal
float fValue { 3.14f };
char letter { 'A' };
void* ptr {};
ptr = &nValue;
ptr = &fValue;
ptr = &letter;
return 0;
}
In these statements, the void pointer ptr is assigned the address of the integer nValue, then a short while after, the address of the floating point variable fValue, and then again a little later, the address of the character letter, all of this as the same pointer. This was all possible because a void pointer has no type of its own keeping it honest; however, if ptr had been defined as an int*, then the subsequent two assignments would have been refused.
This freedom has an immediate price. A void pointer cannot be dereferenced. There is no information from the compiler about the shape or size of the object, and it will not generate the calls to load or store that are required to access it.
#include <iostream>
int main()
{
int value { 10 };
void* voidPtr { &value };
// std::cout << *voidPtr << '\n'; // Will not compile
return 0;
}
The commented out line is illegal. The compiler just can't determine if *voidPtr is an int, a float, or whatever.
If you want to get back to the value, the absent type information must be reintroduced by an explicit cast. Just after the cast, you get a pointer you can actually use.
#include <iostream>
int main()
{
int value { 10 };
// Pass this resource object's address to the constructor
void* voidPtr { &value };
int* intPtr { static_cast<int*>(voidPtr) };
std::cout << *intPtr << '\n';
return 0;
}
The line static_cast<int*>(voidPtr) creates an ordinary int* named intPtr. Once you have created an ordinary pointer, it is legal to dereference it normally, *intPtr. So the line dereferenced produced 10. It is up to the programmer to get the target type right, though; if it is really a float that the void pointer is pointing to, and one casts it to an int*, the read will yield undefined behavior, the bits will be reinterpreted with the wrong set of rules.
The same pattern applies to all types. Here is a second complete program that illustrates a float.
#include <iostream>
int main()
{
float value { 2.5f };
void* voidPtr { &value };
// Perform this conversion explicitly
float* floatPtr { static_cast<float*>(voidPtr) };
std::cout << *floatPtr << '\n';
return 0;
}
Once the cast is performed, the floatPtr pointer is interpreted as pointing to a float, hence the dereference results in 2.5. Altering the cast again to go to the wrong type can again provoke undefined behavior, and the compiler will remain silent.
A void pointer can be assigned to null just as any other pointer:
// Use nullptr when the pointer refers to no object
void* ptr { nullptr };
The null value does no more than indicate that the pointer in question does not point anywhere. The type has been erased in the first place, though, and pointer arithmetic is not allowed: since type information was stripped so that void pointers don't have known sizes, adding or subtracting an integer from them is UB.
This same missing type information prevents the construction of a void reference. Any reference must be bound to a concrete object, with a concrete type whose information we know. void fails to provide that information, and the language tells us it is invalid to have a void reference.
Since void pointers forgo the protection of the type system, it is simple to write code that compiles and yet has unintended behavior at runtime.
Exercise: Void PointersStarter code, hints and solutionOpenClose
Practice: Void Pointers
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
After ptr points to letter, recover a char* using static_cast. Print its character, change it to B, and print letter to demonstrate the shared target.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
int main()
{
int nValue { 5 };
// The f suffix makes this a float literal
float fValue { 3.14f };
char letter { 'A' };
void* ptr {};
ptr = &nValue;
ptr = &fValue;
ptr = &letter;
return 0;
}Hints
The three assignments change which object ptr addresses; they do not convert those objects or copy their values. After the final assignment it points at letter.
This starter prints nothing. If you add a read after the final assignment, cast ptr back to char* before dereferencing; void* itself cannot be dereferenced as a value.
Worked solution
At the last assignment ptr addresses letter. Casting it back to char* recovers the correct pointed-to type; the solution reads A, writes B through that typed pointer, and then prints the changed original letter. No allocation or release is needed.
#include <iostream>
int main()
{
int nValue { 5 };
// The f suffix makes this a float literal
float fValue { 3.14f };
char letter { 'A' };
void* ptr {};
ptr = &nValue;
ptr = &fValue;
ptr = &letter;
char* typed = static_cast<char*>(ptr);
std::cout << *typed << '\n';
*typed = 'B';
std::cout << letter << '\n';
return 0;
}
Example output
A
B
0 comments
No comments yet. Be the first.