C ++ unsafe application

In a complex code base, I have an array of pointers of a non-virtual base class (the base class has no virtual methods)

Consider this code:

#include <iostream> using namespace std; class TBase { public: TBase(int i = 0) : m_iData(i) {} ~TBase(void) {} void Print(void) {std::cout << "Data = " << m_iData << std::endl;} protected: int m_iData; }; class TStaticDerived : public TBase { public: TStaticDerived(void) : TBase(1) {} ~TStaticDerived(void) {} }; class TVirtualDerived : public TBase { public: TVirtualDerived(void) : TBase(2) {} virtual ~TVirtualDerived(void) {} //will force the creation of a VTABLE }; void PrintType(TBase *pBase) { pBase->Print(); } void PrintType(void** pArray, size_t iSize) { for(size_t i = 0; i < iSize; i++) { TBase *pBase = (TBase*) pArray[i]; pBase->Print(); } } int main() { TBase b(0); TStaticDerived sd; TVirtualDerived vd; PrintType(&b); PrintType(&sd); PrintType(&vd); //OK void* vArray[3]; vArray[0] = &b; vArray[1] = &sd; vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK PrintType(vArray, 3); return 0; } 

Output (compiled from Mingw-w64 GCC 4.9.2 on Win64):

 Data = 0 Data = 1 Data = 2 Data = 0 Data = 1 Data = 4771632 

The reason for the failure is that each instance of TVirtualDerived has a pointer to a virtual table that is not in TBase. Thus, bringing to TBase without information of the previous type (from void * to TBase *) is unsafe.

The thing is, I cannot avoid casting in void * in the first place. Adding a virtual method (destructor, for example) to the base class works, but at a cost of memory (which I want to avoid)

Context:

we implement a signal / slot system in a very limited environment (memory is very limited). Since we have several million objects that can send or receive signals, such optimization is effective (when it works, of course)

Question:

How can I solve this problem? So far I have found:

1 - add a virtual method to TBase. It works, but actually it does not solve the problem, it avoids it. And it is inefficient (too much memory)

2 - casting in TBase * instead of casting in void * in the array, due to loss of generality. (maybe I'll try next)

Do you see another solution?

+5
source share
3 answers

You should consider how the class is laid out in memory. TBase easy, just four bytes with one element:

  _ _ _ _ |_|_|_|_| ^ m_iData 

TStaticDerived is the same. However, TVirtualDerived completely different. Now it has alignment 8 and should start with vtable containing the entry for the destructor:

  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| ^ ^ vtable m_iData 

So, when you drop vd to void* and then to TBase* , you will actually TBase* first four bytes of your vtable (offset address in ~TVirtualDerived() ) as m_iData . The solution is to first make static_cast before TBase* , which will return a pointer to the correct TBase starting point in vd , and then to void* :

 vArray[2] = static_cast<TBase*>(&vd); // now, pointer is OK 
+3
source

The problem is that you quit. Since you use a type C listing through void , it is equivalent to reinterpret_cast, which may be unsatisfactory when subclassed. In the first part, the type is available to the compiler, and your casts are equivalent to static_cast.

But I can’t understand why you are saying that you cannot avoid casting in void * in the first place. Since PrintType internally converts void * to TBase * , you can also pass TBase ** . In this case, it will work fine:

 void PrintType(TBase** pArray, size_t iSize) { for(size_t i = 0; i < iSize; i++) { TBase *pBase = pArray[i]; pBase->Print(); } } ... TBase* vArray[3]; vArray[0] = &b; vArray[1] = &sd; vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK PrintType(vArray, 3); 

Alternatively, if you want to use the void ** array, you must explicitly make sure that what you put in it is just TBase * , not a pointer to subclasses :

 void* vArray[3]; vArray[0] = &b; vArray[1] = static_cast<TBase *>(&sd); vArray[2] = static_cast<TBase *>(&vd); PrintType(vArray, 3); 

Both methods are correctly deduced:

 Data = 0 Data = 1 Data = 2 Data = 0 Data = 1 Data = 2 
+4
source

Forget about virtual polymorphism. Do it the old fashioned way.

Add one byte to each TBase to indicate the type and switch statement in the print method to "Do the right thing." (this saves your sizeof (pointer) -1 byte per TBase compared to the virtual method method.

If adding a byte is still too expensive, consider using C / C ++ bit fields (anyone remembers these (grin)) to compress a type field into another field that doesn't fill the available space (like an unsigned integer that has a maximum value of 2 ^ 24 - 1)

The code will be ugly, though, but your tight memory constraints are also ugly. Ugly code that works is better than beautiful code that fails.

0
source

All Articles