Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 802
Adam King Avatar asked Oct 21 '25 10:10

Adam King


1 Answers

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

like image 175
273K Avatar answered Oct 24 '25 00:10

273K