Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with casting a void** to a T**

Tags:

c++

casting

TL;DR: I have a Derived** that I store as a in Lua as a void* userdata. Then I try to get it back as a Base** and stuff breaks. Is there anything I can do or is this all madness that's doomed to failure?

Details:

I'm passing some data back and forth between Lua and C++, and Lua requires the use of void* to store userdata (That I'm using Lua isn't too important, other than that it uses void pointers). Makes sense so far. Lets say I have three classes, Base and Derived, with Derived inheriting from Base. The userdata I feed to Lua is a pointer to a pointer, like so:

template <typename T>
void lua_push(L, T* obj) {
    T** ud = (T**)lua_newuserdata(L, sizeof(T*)); // Create a new userdata
    *ud = obj; // Set a pointer to my object
    // rest of the function setting up other stuff omitted
}

Of course, this is in a nice templated function, so I can pass any of my three types in this way. Later on I can use another templated function to get my userdata out of Lua, like so:

template <typename T>
T* lua_to(lua_State* L, int index) { 
    // there's normally a special metatable check here that ensures that 
    // this is the type I want, I've omitted it for this example
    return *(T**)lua_touserdata(L, index);
}

This works fine when I pass in and out the same type. I'm running into a problem though when trying to pull a Derived out as a Base.

In my specific case, I have a vector being stored on Base. I use lua_push<Derived>(L, obj); to push my object to Lua. Later, in another place I pull it out using Base* obj = lua_to<Base>(L, i);. I then push_back some stuff into my vector. Later on, another portion of code pulls out that exact same object (verified with pointer comparisons) except this time uses Derived* obj = lua_to<Derived>(L, i); My Derived object doesn't see that object that was pushed in. I believe I've narrowed this down to incorrect casting, and I'm probably corrupting some memory somewhere when I make my call to push_back

So my question is, is there a way to make that cast work right? I've tried the various flavors of casts. static_cast, dynamic_cast and reinterpret_cast don't seem to work, either giving me the same wrong answer or not compiling at all.

Specific example:

Base* b = lua_to<Base>(L, -1); // Both lua_to's looking at the same object
Derived* d = lua_to<Derived>(L, -1); // You can be double sure because the pointers in the output match
std::cout << "Base: " << b << " " << b->myVec.size() << std::endl;
std::cout << "Derived: " << d << " " << d->myVec.size() << std::endl;

Output:

Base: 0xa1fb470 1
Derived: 0xa1fb470 0
like image 731
Alex Avatar asked Apr 23 '26 19:04

Alex


2 Answers

The code is not safe. When you cast Base * to void *, you should always cast void * back to Base * first and then cast it again to Derived *. As so:

Derived *obj = ...;
Base** ud = reinterpret_cast<Base **>(lua_newuserdata(L, sizeof(Base*)));
*ud = obj; // implicit cast Derived -> Base
...
Derived *obj = static_cast<Derived *>(*ud); // explicit Base -> Derived

Basically speaking,

Y -> X -> void* -> X -> Y (safe)
Y -> X -> void* -> Y (unsafe)

The reason for this is that the actual pointer value of two pointers pointing to the same object may be different if the two pointers have different types. Whether it works depends on various factors such as inheritance and virtual functions. (It always works in C since C doesn't have those facilities.)

like image 187
Dietrich Epp Avatar answered Apr 25 '26 08:04

Dietrich Epp


This is all very much up to the compiler you're using, but generally the pointer to a base class is the same as a pointer to a derived class. Doing a coercive cast shouldn't hurt anything. The only exception is when there is multiple inheritance involved; a pointer to one base class won't be the same as a pointer to another base class, even with the same object. The compiler needs to know the exact type of the original pointer to properly adjust it, and the cast to void* loses that information.

like image 24
Mark Ransom Avatar answered Apr 25 '26 08:04

Mark Ransom



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!