So, I encountered a weird bug at work when I was trying to reduce the size of a struct by using bit-fields. I managed to isolate the problem and produce a minimal version that replicates the issue. This had the added benefit that MSVC now even warns about what it is going to do: Link to Compiler Explorer. However, Clang and GCC have no problems with this code.
#include <new>
enum foo : unsigned char { zero, one };
struct S
{
int a{ 42 }; //Intentionally not trivial
foo b : 1;
foo c : 1;
};
auto test()
{
constexpr auto size = sizeof (S);
constexpr auto alignment = alignof(S);
struct {
alignas(alignment) unsigned char bytes[size];
} data;
//Buffer overrun on this line
::new(static_cast<void*>(&data)) S{};
//Just to avoid optimizing away the offending instructions
return data;
}
The buffer that I am using should be suitable for storing the object, as it mimics std::aligned_storage
,
and I am invoking True Placement New to store my object in it. I believe this is how f.ex std::vector
works. Despite this, I get this warning:
warning C4789: buffer 'data' of size 8 bytes will be overrun; 5 bytes will be written starting at offset 4
The weird thing is the problem goes away if the braces are replaced with parenthesis (should still be value initialized though, right?) or just removed entirely (default initialized).
The problem also goes away if S
is trivial. Additionally, the problem only occurs with optimizations enabled (had a fun debugging experience because of that).
I do not believe I am invoking undefined behavior here, but the alternative is that there is a bug in VS 2017 and 2019. Currently I am working around the issue by just not using braced initialization and sticking with parenthesis where I suspect there could be issues, but this feels wrong.
Why is this happening, and what can I do to avoid worrying about time bombs in my code? Switching to another compiler is not an option.
Update: So, looking a bit more at the assembly I see that it's still generating suspicious code when using parenthesis to trigger value initialization. Only default initialization produces the expected assembly. Seems quite odd, and I'm definitely suspecting a compiler bug, but I would prefer some more input on this.
This compiler bug is being fixed in VS 2019 16.5.
As a workaround for those who cannot upgrade, consider replacing braces with regular parenthesis, eg replace
S{...};
withS(...);
. If no arguments are being supplied to the constructor, consider simply removing the braces as the object is still default-constructed this way.