Following the discussion in Vptr with default constructors vs explicitly declared constructors the way I define my constructors should not matter. However, I've run into an error that only happend when using the default constructor. Here is a Godbolt that highlights the effect, although the error is optimized away: https://godbolt.org/z/7K6K9PYff
I construct a global variable IODeviceWrapper<Logger> wrapper;
once using the default constructor IODeviceWrapper() = default;
and once using my own IODeviceWrapper() : IODevice() {}
. To me, these should be identical. However they produce different assembly:
_GLOBAL__sub_I_wrapper:
mov edx,0x404038
mov esi,0x404040
mov edi,0x401190
jmp 401030 <__cxa_atexit@plt>
versus
_GLOBAL__sub_I_wrapper:
mov edx,0x404038
mov esi,0x404050
mov edi,0x401190
mov QWORD PTR [rip+0x2fb6],0x402078 # 404050 <wrapper>
jmp 401030 <__cxa_atexit@plt>
It doesn't matter to the code in the example later, because the correct function is called directly. Still, it seems to me that only the user-defined constructor correctly sets up the virtual table pointer. Only the latter allows me to access functions from IODevice
through my wrapper, while the default-defined example breaks.
Am I at fault here?
EDIT:
I tried recreating the circumstances of my failure, but that seems harder than I thought. My specific problem occurs when using arm-none-eabi-g++
in version 10.3.1 20210824 to compile a binary for an STM32F429 using modm. The program breaks when simulated using renode. Using this configuration, I get a combination where the vptr setup is optimized away, but the call to the virtual function is not inlined - thus breaking the execution.
In all other configurations (x86_64 gcc version 9 or 10, aarch64-linux-gnu-gcc version 9 or 10, and arm-linux-gnueabihf-gcc version 9) it seems that when the vptr setup is optimized away, then the virtual call is replaced as well. When it is not replaced, then the vptr setup is not optimized away. In any case, the resulting binaries work.
I will investigate further, but for now, I am okay with using the user-defined constructor, which is not optimized as per @user17732522 's commentary.