![]() |
Sponsored Link •
|
Advertisement
|
vptr
which points to a table of
functions called the vtbl[]
. A virtual function call is
performed by using the vptr
to find the vtbl[]
,
and then calling the function at a specific index into that
vtbl[]
. Therefore, the polymorphic behavior of a class
object is controlled by where the hidden vptr
member is
pointing. The vptr
member is set when the object is
constructed by some hidden code added by the compiler to every constructor
for that object's class.
So, by manipulating the vptr
ourselves, we can control the behavior of an object, even
change its type, without calling a constructor. This technique is called vptr
jamming.
For example, consider a collection class which needs to be very fast. Most of the time, it will be used in a single-threaded manner, but sometimes, in a multithreaded manner. The program can go back and forth between single and multithreaded more than once during execution. It's got to run as fast as possible, so in single-threaded mode the time spent to do locks is unaffordable. So, our class might look like:
struct Collection { ... members of the collection ... virtual void foo() = 0; }; struct SingleThreadedCollection : Collection { void foo() { ... optimized for single threaded ... } }; struct MultiThreadedCollection : Collection { void foo() { ... synced for multithreaded ... } };The desire is to switch back and forth between single and multithreaded operations without having to destruct and reconstruct the object—we want to dynamically change the behavior by switching (i.e. jamming) the
vptr
. Here's how that would look:
struct Collection { ... members of the collection ... virtual void foo() = 0; void toSingle(); void toMulti(); }; struct SingleThreadedCollection : Collection { static SingleThreadedCollection tmp; void foo() { ... optimized for single threaded ... } }; struct MultiThreadedCollection : Collection { static MultiThreadedCollection tmp; void foo() { ... synced for multi threaded ... } }; SingleThreadedCollection SingleThreadedCollection::tmp; MultiThreadedCollection MultiThreadedCollection::tmp; void Collection::toSingle() { *(void **)this = *(void **)&SingleThreadedCollection::tmp; } void Collection::toMulti() { *(void **)this = *(void **)&MultiThreadedCollection::tmp; }The assignment in the
toXxxx()
functions gets the value of
the right vptr
from the static temporary tmp
created for just that purpose, and jams it into the vptr
location in *this
. For most compilers, the vptr is at offset
0 of the struct. For the rest, this code will have to be tweaked to
account.
Naturally, there are problems that must be avoided or accounted for here as well:
vptr
at different offsets within the object instance. A bit of simple testing and looking at
the generated assembler will quickly find it.
vptr
will be
at the same offset. This is true of all the compilers I tried it on, but there is certainly no
guarantee that this is true.
Sponsored Links
|