I have seen and used this many times is C++, specially in various thread implementations. What I wonder is if there are any pitfalls/ issues of doing this? Is there any way that we could run in to an error or undefined condition when we are casting to void* and back again? How should we resolve such issues if there are any?
7 Answers
This is perfectly valid. Here is what the standard has to say about it:
§4.10 Pointer conversions
- An rvalue of type "pointer to cv
T," whereTis an object type, can be converted to an rvalue of type "pointer to cvvoid." The result of converting a "pointer to cvT" to a "pointer to cvvoid" points to the start of the storage location where the object of typeTresides, as if the object is a most derived object (1.8) of typeT(that is, not a base class subobject).
which means you can convert your pointer to class into a void pointer. And ...
§5.2.9 Static cast
- An rvalue of type "pointer to cv
void" can be explicitly converted to a pointer to object type. A value of type pointer to object converted to "pointer to cvvoid" and back to the original pointer type will have its original value.
which means you can use static_cast to convert a void pointer back into an original class pointer.
I have not seen casting to void* much in C++. It is a practice in C that is actively avoided in C++.
Casting to void* removes all type safety.
If you use reinterpret_cast or static_cast to cast from a pointer type to void* and back to the same pointer type, you are actually guaranteed by the standard that the result will be well-defined.
The hazard is that you may cast a void* to the wrong type, since you are no longer assured of what the correct type was.
Comments
What I wonder is if there are any pitfalls/ issues of doing this?
You need to be absolutely sure while casting the the void* back to the particular type, if you don't, you end up with an Undefined behavior and a potential disaster. Once you use void * you lose type safety.It is difficult to keep track of what type a void * is actually pointing to, there is no way to guarantee or determine that it indeed points to the type to which you are going to typecast it back to.
Is there any way that we could run in to an error or undefined condition when we are casting to void* and back again?
Yes, the scenario mentioned in #1.
How should we resolve such issues if there are any?
Avoid using void * in C++ completely, instead use templates and inheritance.
In C you might absoultely need it in certain situations but try to keep its use to a minimum.
Bottomline,
C/C++ allows you to shoot yourself in foot, it is up to you to do or not do so.
1 Comment
std::any is a safer alternative: std::any dt = foo; //< dt holds a CFoo*. then foo = std::any_cast<CFoo*>(dt); will throw if dt somehow doesn't hold a CFoo*, rather than invoking UB when you try to dereference a pointer to the wrong type.The only thing the standard grants is that, given A* pa, (A*)(void*)pA == pA. A a consequence
void* pv = pA; A* pA2 = (A*)pv; pA2->anything ... will be te same as pA->anything ...
Everything else is "not defined", ad -in fact- is somehow implementation dependent.
Based on my experience, here are some known pitfalls:
- Consider
Aderived formB,pAandpBto beA*andB*.pB=pAmakespBto point to the base ofA. That doesnt mean thatpBandpAare the same address. hencepB = (B*)(void*)pAcan actually point anywhere else into A (although single inheritance objects are commonly implemented sharing the same origin, so it apparently works fine) - The same is viceversa: Assuming
pBactually is pointing to anA,pA = (A*)(void*)pBdon't necessarily point correctly to the A object. The correct way ispA = static_cast<A*>(pB); - If the above points can work with the most of single inheritance implementations, will never work with multiple imheritance for bases other than the first: consider
class A: public Z, public B { ... };ifZis not empty, given anA, theBsubcomponent will not have the same A address. (and multiple inheritance in C++ is everywhere an iostream is) - Sometimes things depend also on the platform:
(char*)(void*)pI(wherepIpoints to an integer) will not be the same as "*pIif*pIin(-128..+127)" (it will be only on little endian machines)
In general don't assume conversion between types works just changing the way an address is interpreted.
Comments
Is this valid?
Yes, it is valid as per standard § 5.2.9.7
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value. [ Example:
T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void*>(p1)); bool b = p1 == p2; // b will have the value true. Comments
I know a lot of functions within driver etc. using void pointers to return data to the caller, the schema is mostly the same:
int requestSomeData(int kindOfData, void * buffer, int bufferSize); This function can take different data types as parameter. What they do is using bufferSize as a parameter to avoid writing to memory places they should not write to. If the bufferSize does not match or is smaller than the data that shall be returned, the function will return an error code instead.
Anyway: Avoid using them or think threefold before writing any code.
void*...void*is quite a powerful construct, but just as dangerous because there is just no way to tell why something goes wrong. you just have to be VERY careful when you use it.void *.