0

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.

  • "the way I define my constructors should not matter. " Incorrect. It matters greatly. – Taekahn Apr 05 '22 at 15:35
  • 1
    @Taekahn "...should not matter to the _virtual table initialization_" is what I meant ofc :) – HairyNopper Apr 05 '22 at 15:37
  • Do you have a [mre] where this is actually breaking something? The compiler is allowed to do whatever it wants as long as it does not change the observable effects of the code. – NathanOliver Apr 05 '22 at 15:40
  • @NathanOliver my actual example is rather big and compiled for an STM32 device. But I'll try and see if I can break it on x86 – HairyNopper Apr 05 '22 at 15:41
  • In your example with the defaulted constructor the variable's initialization is constant initialization. With your user-provided constructor (which is not marked `constexpr`) it is dynamic initialization. Not sure whether this is just an artifact of how you present the example or actually what you wanted to know. – user17732522 Apr 05 '22 at 16:28

0 Answers0