The next thing to test is having abstract classes with pure virtual functions while excluding linkage to standard library (using -nostdlib compilation option). Below is an excerpt from test_cpp_abstract_class application.
CMakeFiles/04_test_abstract_class.dir/AbstractBase.cpp.o: In function `AbstractBase::~AbstractBase()':
AbstractBase.cpp:(.text+0x24): undefined reference to `operator delete(void*)'
CMakeFiles/04_test_abstract_class.dir/AbstractBase.cpp.o:(.rodata+0x10): undefined reference to `__cxa_pure_virtual'
CMakeFiles/04_test_abstract_class.dir/Derived.cpp.o: In function `Derived::~Derived()':
Derived.cpp:(.text+0x3c): undefined reference to `operator delete(void*)'
The __cxa_pure_virtual is a function, address of which compiler writes in the virtual table when the function is pure virtual. It may be called due to some unnatural pointer abuse or when trying to invoke pure virtual function in the destructor of the abstract base class. The call to this function should never happen in the normal application run. If it happens it means there is a bug. It is quite safe to implement this function with infinite loop or some way to report the error to the developer, by flashing leds for example.
extern"C"void__cxa_pure_virtual() { while (true) {} }
The requirement for operator delete(void*) is quite strange though, there is no dynamic memory allocation in the source code. It has to be investigated. Let's stub the function and check the output of the compiler:
voidoperator delete(void*) { }
The virtual tables for the classes reside in .rodata section:
The last entry for both classes has the address of AbstractBase::nonOverridenFunc function:
000080e8 <_ZN12AbstractBase16nonOverridenFuncEv>:
80e8: e12fff1e bx lr
The third entry in the virtual table of Derived class has the address of Derived::func function, while the third entry in the virtual table of AbstractBase class has the address of __cxa_pure_virtual, just like expected.
0000810c <_ZN7Derived4funcEv>:
810c: e12fff1e bx lr
0000815c <__cxa_pure_virtual>:
815c: eafffffe b 815c <__cxa_pure_virtual>
The first two entries in the virtual tables point to two different implementations of the destructor. The first entry has the address of normal destructor implementation, and the second one has an address of the second destructor implementation, that invokes operator delete (has _ZdlPv symbol) after the destruction of the object:
It seems that when there is a virtual destructor, the compiler will have to support direct invocation of the destructor as well as usage of operator delete. In case of the former the compiler will use the first entry in the virtual table for the destructor invocation, and in case of the latter the compiler will use the second entry. Let's try to add the following lines to our main function:
basePtr->~AbstractBase(); delete basePtr;
The compiler will add the following instructions to the main function:
The address of the virtual table is written into r3, then value of r3 is overwritten with address of the destructor function to call, and the call is executed using blx instruction. The first invocation takes the address of destructor function from the first entry of virtual table, while the second invocation takes the address from second entry (offseted by #4). This is just like expected.
CONCLUSION: Having virtual destructor may require an implementation of operator delete(void*) even if there is no dynamic memory allocation.