缓冲区因位域和值初始化而溢出 - 编译器错误或未定义的行为?

Buffer overrun with bit-fields and value initialization - compiler bug or undefined behavior?

所以,当我试图通过使用位域来减小结构的大小时,我在工作中遇到了一个奇怪的错误。我设法隔离了问题并生成了一个可复制问题的最小版本。这有一个额外的好处,MSVC 现在甚至会警告它要做什么:Link to Compiler Explorer。但是,Clang 和 GCC 对这段代码没有任何问题。

#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;
}

我使用的缓冲区应该适合存储对象,因为它模仿 std::aligned_storage, 我正在调用 True Placement New 将我的对象存储在其中。我相信这就是 f.ex std::vector 的工作原理。尽管如此,我还是收到了这个警告:

warning C4789: buffer 'data' of size 8 bytes will be overrun; 5 bytes will be written starting at offset 4

奇怪的是,如果将大括号替换为圆括号(但仍应进行值初始化,对吗?)或完全删除(默认初始化),问题就会消失。

如果 S 是微不足道的,问题也会消失。此外,该问题仅在启用优化时出现(因此获得了有趣的调试体验)。

我不相信我在这里调用未定义的行为,但另一种情况是 VS 2017 和 2019 中存在一个错误。目前我正在解决这个问题,只是不使用大括号初始化并坚持使用括号怀疑可能有问题,但这感觉不对。

为什么会发生这种情况,我该怎么做才能避免担心我的代码中出现定时炸弹?切换到另一个编译器不是一个选项。

更新: 因此,仔细查看程序集,我发现它在使用括号触发值初始化时仍在生成 可疑 代码。只有默认初始化会生成 expected 程序集。看起来很奇怪,我肯定怀疑是编译器错误,但我更希望对此有更多的投入。

这个编译器错误是 being fixed in VS 2019 16.5

作为无法升级的解决方法,请考虑将大括号替换为常规括号,例如将 S{...}; 替换为 S(...);。如果没有向构造函数提供任何参数,请考虑简单地删除大括号,因为对象仍然以这种方式默认构造。