09 November, 2017

C++11 and MSVC Gone Wrong

It's been few weeks I started switching all my projects to C++11. The reason is simple - it has been standardized for years and all major compilers now state complete (or almost complete) C++11 support. I thought that it's time to switch to C++11 and to update all my projects, well... was a good decision, but I found a severe bug in both VS2015 and VS2017 compilers that made the transition a nightmare...

Bugs happen and usually there are workarounds, however, this time I found one only for VS2017, which means that all my projects will require at least VS2017 to work (GCC and clang are unaffected). The tricky part of the issue is that VS compiles the code just without errors making it fail at runtime. This means that you have to run the code to actually discover the problem. I first discovered the bug by running AsmJit test suite where some really basic tests surprisingly started failing.

In this post I finally decided to dig into the issue a bit more and to create a repro that demonstrates it.

The Issue

MSVC doesn't like constant expressions in struct that contains a union. What happens is that instead of initializing the first member of the union it zero initializes it instead. I found it very confusing that even if you guard your code by static_assert to verify the initialized value it passes the assertion! Here is the code:

struct Data {
  struct A { int x, y; };
  struct B { int x, y; };
  union {
    A a;
    B b;
  };
};

class Object : public Data {
public:
  constexpr Object() noexcept 
    : Data {{ 0, 0 }} {}
  constexpr Object(int x, int y) noexcept
    : Data {{ x, y }} {}
};

int func() {
  constexpr Object obj(1, 2);
  static_assert(obj.a.x == 1);
  return obj.a.x;
}

I tested all compilers (GCC, Clang, Intel, MSVC) and all compile the code just fine. The function should return '1', but MSVC compiles it to return '0' even when the static_assert(obj.a.x == 1) passes:

; GCC/Clang/Intel output
func():
  mov eax, 1
  ret

; MSVC output
func PROC
  xor eax, eax
  ret 0
func ENDP

Snaky, right?

VS2017 Workaround

I was able to trick the compiler to compile the code correctly by adding an array as a first member of the union:

struct Data {
  struct A { int x, y; };
  struct B { int x, y; };
  union {
    int data[2];
    A a;
    B b;
  };
};

class Object : public Data {
public:
  constexpr Object() noexcept 
    : Data {{{ 0, 0 }}} {}
  constexpr Object(int x, int y) noexcept
    : Data {{{ x, y }}} {}
};

int func() {
  constexpr Object obj(1, 2);
  static_assert(obj.data[0] == 1);
  return obj.a.x;
}
All compilers except VS2015 compile this code correctly:
; GCC/Clang/Intel output
func():
  mov eax, 1
  ret

; MSVC output
func PROC
  mov eax, 1
  ret 0
func ENDP

Conclusion

I found this bug to be already reported here, but it seems it's not interesting to VS team and I'm not gonna invest any more time into this.

You can use a compiler explorer to test it yourself. If you know how to workaround this in VS2015 please leave a note here, I'm interested unless it requires a complete code rewrite.

UPDATE

It seems the issue has been resolved, however, the fix will not be available as an update to the broken VS2015/VS2017 products.