Search code examples
c++visual-c++c++20undefined-behavior

Members wrongly initialized in nested aggregate? (MSVC)


Update: Since this appears to be a compiler bug, I've submitted a report to Microsoft.

The output of the following code surprises me. My expectation is that Inner would have num1 initialized to 10 (as it is) and then num2 would be initialized, copying num1's value of 10. So the output would be 10 10. Instead, I get 10 4.

#include <iostream>

struct Inner {
    uint64_t num1{10};
    uint64_t num2{num1};
};

struct Outer {
    uint64_t a{};
    Inner inner{};
};

Outer makeOuter() {
    return {.a = 4};
}

int main(int argc, char* argv[]) {
    auto outer = makeOuter();
    std::cout << outer.inner.num1 << " " << outer.inner.num2 << std::endl;
    return 0;
}

num2 appears to copy uninitialized memory (or from the wrong address?). It receives the value of Outer's member a, whether that is made to be 4 or 0xFFFFFFFFFFFFFFFF.

The compiler is MSVC 19.37.32825.0. Compile for example by running cl main.cpp /std:c++latest /EHsc /O2 /link /out:program.exe.

With clang++ or g++, I get the expected result, 10 10. Compiler bug or my misunderstanding?

The effects of some other changes:

  • Adding a constructor Inner() {} to Inner -> output becomes 10 10
  • Changing Inner inner{} to Inner inner{11, 12} -> output becomes 11 12 (expected)
  • Changing Inner inner{} to Inner inner{11} -> output becomes 11 4
  • Returning {} instead of {.a = 4} -> output becomes 10 10
  • Creating Inner inner{} directly in main and printing its members -> output becomes 10 10

Solution

  • It's a compiler bug.

    makeOuter() is translated into

    Outer makeOuter(void) PROC               ; makeOuter, COMDAT
            mov     eax, DWORD PTR __$ReturnAddress$[esp-4]
            mov     DWORD PTR [eax], 4
            mov     DWORD PTR [eax+4], 0
            mov     DWORD PTR [eax+8], 10           ; 0000000aH
            mov     DWORD PTR [eax+12], 0
            mov     DWORD PTR [eax+16], 4  ; <-- HERE
            mov     DWORD PTR [eax+20], 0
            ret     0
    Outer makeOuter(void) ENDP               ; makeOuter
    

    If it's changed like

    Outer makeOuter() {
        return {.a = 4, .inner = {}};
    }
    

    then it's translated into

    Outer makeOuter(void) PROC               ; makeOuter, COMDAT
            mov     eax, DWORD PTR __$ReturnAddress$[esp-4]
            mov     DWORD PTR [eax], 4
            mov     DWORD PTR [eax+4], 0
            mov     DWORD PTR [eax+8], 10           ; 0000000aH
            mov     DWORD PTR [eax+12], 0
            mov     DWORD PTR [eax+16], 10                    ; 0000000aH
            mov     DWORD PTR [eax+20], 0
            ret     0
    Outer makeOuter(void) ENDP               ; makeOuter
    

    https://godbolt.org/z/vdb4Yv6n8