Why can't the C ++ virtual function defined in the header be compiled and bound in vtable?

The situation is as follows. I have a library containing a class definition -

QueueClass : IClassInterface { virtual void LOL() { do some magic} } 

My shared library initializes a class member

 QueueClass *globalMember = new QueueClass(); 

My export export library is a C function that returns a pointer to globalMember -

 void * getGlobalMember(void) { return globalMember;} 

My application uses globalMember like this

 ((IClassInterface*)getGlobalMember())->LOL(); 

Now it’s very uber - if I don’t reference LOL from the shared library, then LOL is not bound and calls it from the application that throws an exception. Cause. VTABLE contains nul instead of a pointer to the LOL () function.

When I transfer the definition of LOL () from the .h file to .cpp, it suddenly appears in VTABLE and everything works just fine. What explains this behavior ?! (gcc compiler + ARM _ architecture)

+6
c ++ interface virtual
source share
5 answers

The linker here is the criminal. When a function is inline, it has several definitions, one in each cpp file referenced. If your code never refers to a function, it is never generated.

However, the vtable layout is defined at compile time with the class definition. The compiler can easily say that LOL() is a virtual function and should have an entry in the vtable .

When it receives a time reference for the application, it tries to fill in all the QueueClass::_VTABLE , but does not find the LOL() definition and leaves it empty (null).

The solution is the LOL() link in the file in the shared library. Something simple like &QueueClass::LOL; . You may need to assign it to the throw variable in order to make the compiler stop complaining about claims without effect.

+6
source share

I do not agree with @sechastain .

Attachment is not automatic. Regardless of whether the method is in place or a hint (the inline or __forceinline ), the compiler is the only one who can decide whether the attachment will actually take place and uses complex heuristics for this. However, one specific case is that it should not cause a call when invoking a virtual method using dispatch at run time, precisely because dispatch and insert are incompatible.

To understand the accuracy of "using runtime scheduling":

 IClassInterface* i = /**/; i->LOL(); // runtime dispatch i->QueueClass::LOL(); // compile time dispatch, inline is possible 

@0xDEAD BEEF : I think your design is fragile, to say the least.

Using C-Style is wrong in this case:

 QueueClass* p = /**/; IClassInterface* q = p; assert( ((void*)p) == ((void*)q) ); // may fire or not... 

In principle, there is no guarantee that the 2 addresses are equal: this implementation is defined and is unlikely to resist change.

I want you to be able to safely use the void* pointer for the IClassInterface* pointer, then you need to first create it from IClassInterface* so that the C ++ compiler can do the correct pointer arithmetic depending on the layout of the objects.

Of course, I will also emphasize than using global variables ... you probably know this.

Regarding the reason for the absence? I honestly don't see anything but an error in the compiler / linker. Several times I saw the built-in definition of virtual functions (more specifically, the clone method), and it never caused problems.

EDIT : since “proper pointer arithmetic” was not so well understood, here is an example

 struct Base1 { char mDum1; }; struct Base2 { char mDum2; }; struct Derived: Base1, Base2 {}; int main(int argc, char* argv[]) { Derived d; Base1* b1 = &d; Base2* b2 = &d; std::cout << "Base1: " << b1 << "\nBase2: " << b2 << "\nDerived: " << &d << std::endl; return 0; } 

And here is what was printed:

 Base1: 0x7fbfffee60 Base2: 0x7fbfffee61 Derived: 0x7fbfffee60 

Not the difference between b2 and &d , although they refer to the same object. This can be understood if you think about the layout of the memory object.

 Derived Base1 Base2 +-------+-------+ | mDum1 | mDum2 | +-------+-------+ 

When converting from Derived* to Base2* compiler will perform the necessary settings (here, increase the address of the pointer by one byte) so that the pointer is effective by pointing to Base2 part of Derived and not to part Base1 , mistakenly interpreted as an object Base2 (which would be nasty) .

This is why the use of C-Style casts should be avoided in suppression. Here, if you have a Base2 pointer, you cannot interpret it as a Derived pointer. Instead, you will need to use static_cast<Derived*>(b2) , which will reduce the pointer by one byte so that it correctly points to the beginning of the Derived object.

Manipulating pointers is usually called pointer arithmetic. Here, the compiler will automatically perform the correct configuration ... provided that the type is recognized.

Unfortunately, the compiler cannot execute them when converting from void* , so the developer must make sure that he does this correctly. A simple rule of thumb: T* -> void* -> T* with the same type that appears on both sides.

Therefore, you should (simply) fix your code by declaring: IClassInterface* globalMember , and you would not have a portability problem. You will probably still have a maintenance problem, but a problem with using C with OO code: C is not aware of any object oriented things.

+2
source share

I assume GCC is using the ability to inline the LOL call. I will see if I can find a link for you on this ...

I see that sechastain beat me up to a more detailed description, and I could not find the link I was looking for. Therefore, I will leave it.

+1
source share

The functions defined in the header files are embedded in use. They do not compile as part of the library; instead, when the call is made, the function code simply replaces the call code, and this is what compiles.

So, I am not surprised to see that you are not finding a v-table entry (what does this indicate?), And I am not surprised to see that moving the function definition to a .cpp file suddenly makes things work. I'm a little surprised that instantiating an object with a call in the library matters, however.

I'm not sure this is in a hurry on your part, but from the code provided by IClassInterface, it does not necessarily contain LOL, only QueueClass. But you call the IClassInterface pointer to call LOL.

0
source share

If this example is simplified and your actual inheritance tree uses multiple inheritance, this can easily be explained. When you cast to a pointer to an object, the compiler must adjust the pointer so that the corresponding vtable refers to it. Since you are returning void * , the compiler does not have the necessary information to configure.

Edit: There is no standard C ++ object for the layout, but for one example of how multiple inheritance can work, see this article from Bjarn Straustrap himself: http://www-plan.cs.colorado.edu/diwan/class- papers / mi.pdf

If this is really your problem, you can fix it with one simple change:

 IClassInterface *globalMember = new QueueClass(); 

The C ++ compiler will make the necessary pointer changes when it completes the assignment, so that the C function will return the correct pointer.

0
source share

All Articles