Lets assume that we have following code:
struct VeryComplexStruct {
// a very complex struct
void test() {}
};
void foo(VeryComplexStruct **ptr) {
// do something
}
int main() {
VeryComplexStruct *p = nullptr;
foo(&p);
p->test();
}
I wonder if the code is still valid by C standard if we just use void*
and cast it like this:
struct VeryComplexStruct {
// a very complex struct
void test() {}
};
void foo(VeryComplexStruct **ptr) {
// do something
}
int main() {
void *p = nullptr;
foo(reinterpret_cast<VeryComplexStruct **>(&p));
(static_cast<VeryComplexStruct *>(p))->test();
}
This may happen if we use C
libraries in C
. Any idea?
CodePudding user response:
if the code is still valid
The code, as presented, (ignoring that p
points to nullptr and assuming p
points to a valid VeryComplexStruct
object) is valid - you can convert between pointer values. However, if you would access the pointer:
struct VeryComplexStruct {
void test() {}
};
void foo(VeryComplexStruct **ptr) {
*ptr; // this here
}
int main() {
void *p = new VeryComplexStruct();
foo(reinterpret_cast<VeryComplexStruct **>(&p));
(static_cast<VeryComplexStruct *>(p))->test();
}
That would be invalid - you can't access void **
pointer with VeryComplexStruct **
handle, so it kind of defeats the whole purpose. You have to access void **
using void **
handle, like reinterpret_cast<VeryComplexStruct*>(*reinterpret_cast<void**>(ptr))
.
Any idea?
Static typed languages is a feature to help you with types - using void *
you are throwing it all out the window. Stick to types, and only cast to void *
when you need to.
CodePudding user response:
reinterpret_cast<VeryComplexStruct **>(&p)
There is no guarantee that the alignment requirements of void*
and VeryComplexStruct*
are compatible. If they are not and &p
is not suitably aligned for a VeryComplexStruct*
, then the value resulting from this cast will be unspecified and using it in basically any way will cause undefined behavior.
Even if the alignment requirement is satisfied, the resulting pointer may not be used to access the object it points to. There is no VeryComplexStruct*
object which is pointer-interconvertible with the void*
object at the address &p
. Therefore the result of the cast will still point to the void*
object, not a VeryComplexStruct*
object.
Generally it is an aliasing violation, causing undefined behavior, to access an object of one type through an lvalue (e.g. a derereferenced pointer) of another type with a few specific exceptions, none of which apply here.
(There isn't even any guarantee that void*
and VeryComplexStruct*
have the same size and representation, although that is practically generally the case.)
(static_cast<VeryComplexStruct *>(p))->test();
Assuming the function has not modified p
, this is trying to call a non-static member function on a null pointer, causing undefined behavior.
If the function did modify p
in some way, which is legal basically only by first casting the argument back to void**
, then it depends on what the function did do with p
. The line is valid if p
was assigned a pointer to a VeryComplexStruct*
object or some object which is pointer-interconvertible with such. Otherwise the member function call is again going to have undefined behavior.
This may happen if we use C libraries in C . Any idea?
It causes undefined behavior in the same way in C, although the object model and terminology used there is different. In that case the problem would be that void*
and VeryComplexStruct*
are not compatible types, so accessing the void*
object through a pointer to VeryComplexStruct*
would again be an aliasing violation. So if something like this is used in a C library, it already relies on undefined behavior.