Sign in to follow this  
Shinkage

Memory Offset of Pointer Casts in C++

Recommended Posts

Shinkage    595
A project I'm currently working on requires casting from classes in a hierarchy to void* and subsequently casting from that void* to a [i]different[/i] class in the same hierarchy. Now, I'm fully aware that naively speaking this isn't a valid thing to do, because casting among classes in a hierarchy may introduce offsets into the actual physical pointer. For example:

[source lang="cpp"]class Base1 { ... }
class Base2 { ... }

class Derived : public Base1, public Base2 { ... }
// ---------------------
Derived *d = new Derived;
Base2 *b2 = d; // b2 will probably point to a different place in memory from d[/source]
That being said, my current approach involves storing the offset for valid casts along with the void*, and adding it to the pointer value when I retrieve it.

Now that I've gotten the preliminaries out of the way, my question is this: will the offset of casting any derived class to any base class [i]always[/i] be the sum of the offsets of every intermediate cast? My initial thought is that it would have to be because of the associativity (? .. not sure if this is precisely the right term to express what I'm thinking) of pointer casting, but can anybody see any problems with my assumptions here? It's been a while since I've had to deal with this particular detail of the way pointers and inheritance work.

Share this post


Link to post
Share on other sites
Krohm    5030
You're playing with [b][color="#8b0000"]fire [/color][/b]here.
Cannot you use [font="Courier New"][color="#0000ff"]dynamic_cast[/color][/font]? It is more informative to the compiler and does require you to mess up with the offsets.
Unfortunately, this only works for objects having a VFTABLE but that's seldom a problem - find a way to get the rid of void* and you're done.
[b]I don't think the offset will be the sum of the base classes.[/b] Surely not if virtual data members are involved. In general this is difficult to say without looking at each class' implementation. It is very likely this holds true in many cases but I have no idea on how to check if this property holds for an arbitrary class.
I don't even need this, as I use [color="#0000FF"]dynamic_cast[/color], which does all the thing for me as well as checking the type safety.

If casting to void* is absolutely required - I hardly believe you cannot figure out a meaningful base class - then using POD is perhaps the only reasonable way to do... or maybe checking VFTABLE offset (compiler dependant). I wouldn't know where to begin without losing my sleep at night!

Share this post


Link to post
Share on other sites
Shinkage    595
[quote name='Krohm' timestamp='1306774068' post='4817568']
You're playing with [b][color="#8b0000"]fire [/color][/b]here.
[/quote]

Sometimes you have to play with fire. I can say with absolute certainty that there is [b]no[/b] way to a void* intermediate stage; the problem comes about with respect to interfacing with a virtual machine that is totally ignorant of the C++ type system. The interface requires passing arbitrary types which may or may not be derived from any arbitrary number of bases, and those types being manipulable by the virtual machine as well as the host program.

Consider the following code with comments to maybe better explain my thought process:
[source lang="cpp"]
struct Base1 { ... };
struct Base2 { ... };
struct Intermediate1 { ... };
struct Intermediate2 : public Base1, public Base2 { ... }
struct Derived : public Intermediate1, public Intermediate2 { ... }

Derived *d = new Derived;
Intermediate2 *i2 = d;
// i2 now points to a location in memory at d+offset
Base2 *b2 = i2;
// b2 now points to a location in memory at i2+offset'

// Since i2=d+offset and b2=i2+offset', then logically it would seem as if it must
// be the case that b2=d+offset+offset'. And further:
b2 = d;
// This must result in the same memory address as the combination of the two casts above,
// or at least I would assume it would have to.

// In other words, if a cast introduces a memory offset, then that offset must be uniform
// across all such casts, because the compiler can't know what type has actually been instantiated.[/source]
[i]Caveat: Virtual [b]inheritance[/b] is not being considered--casts to/from virtually inherited (public virtual Base) base classes are not supported in this interface.[/i]
[i]Caveat 2: My logic may be [b]completely[/b] off base here, and that's why I'm asking the internet![/i]

Share this post


Link to post
Share on other sites
Prefect    373
This doesn't explain why you believe your offset voodoo to be necessary. The proper way to deal with such things is to remember which type the void* was cast from, and then first cast back to that exact same type. Then, afterwards, you use C++ style casts to get the right behaviour.

Off the top of my head (though I haven't tested it), I'm thinking of something like:
[code]
class Base1 { };
class Base2 { };
class Derived : public Base1, public Base2 { };

Derived * d = ....;

void * ptr = d;

// then hand ptr to C

// elsewhere, get ptr from C
Derived * d = static_cast<Derived *>(ptr);
Base2 * b2 = d;
[/code]
This way, you avoid offset magic entirely, and your code is actually correct (in the sense that it is guaranteed to work).

Share this post


Link to post
Share on other sites
Shinkage    595
[quote name='Prefect' timestamp='1306785491' post='4817635']
This doesn't explain why you believe your offset voodoo to be necessary. The proper way to deal with such things is to remember which type the void* was cast from, and then first cast back to that exact same type. Then, afterwards, you use C++ style casts to get the right behaviour.[/quote]

The problem with this is the logic behind the casting takes place inside the virtual machine when it calls back into the host environment and, as I said, the virtual machine is ignorant of the C++ type system. I suppose one option would be to register a conversion function for every possible conversion, but I'm working with some target platforms with limited system memory and that would definitely increase the executable size a fair bit in addition to increasing the memory footprint of the virtual machine.

Share this post


Link to post
Share on other sites
Palidine    1315
I've been burned like this before. If you can remember the exact type as Prefect mentions, then that is absolutely the best. However, if you cannot, then you want to cast to void* from a guaranteed known Base class:

[code]
class B : public A
{
};

class C : public A
{
};
[/code]

I believe we did something like this

[code]
B *b;
C *c;

void *asVoidB = reinterpret_cast<void*>(static_cast<A*>(b));
void *asVoidC = reinterpret_cast<void*>(static_cast<A*>(c));
[/code]

Later, you can then retrieve it and know that the void* is exactly an A* (so reinterpret_cast<A*>(myVoid) will be safe). Then you can do a "safe" static_cast back to what it is, or hopefully just be able to access it via polymorphism. Then you won't have offset hassle. Otherwise, you're just going to have broken code where you have reinterpret_cast the void* directly to a B* or a C* but you are pointing at the wrong part of the object in memory. In that case you'll hopefully have catastrophic bugs, but more likely, as was my case, you'll have bugs that take an entire week to figure out and which are almost completely un-reproducible.

-me

Share this post


Link to post
Share on other sites
Shinkage    595
The replies have convinced me to reduce the scope of the interface and only allow registration of class hierarchies which derive from the library's shared pointer base. It may not be quite as general purpose, but it's going to be a hell of a lot simpler [b]and[/b] totally standard-compliant as well. Thanks.

Share this post


Link to post
Share on other sites
Krohm    5030
As a side note,
have you considered putting virtual object blobs in "real" application memory instead of vice-versa?
If you have a way to track object creation you might eventually manage to keep the correct type information since the beginning.

Share this post


Link to post
Share on other sites
Shinkage    595
Not really sure what you're suggesting here. Type information is already tracked perfectly, the problem is that the conversion is initiated from the virtual machine which doesn't partake of the C++ type system. The only option (as far as I can figure) for doing the conversion in the host would be to register a conversion function for every possible conversion.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this