缓冲区因位域和值初始化而溢出 - 编译器错误或未定义的行为?
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(...);
。如果没有向构造函数提供任何参数,请考虑简单地删除大括号,因为对象仍然以这种方式默认构造。
所以,当我试图通过使用位域来减小结构的大小时,我在工作中遇到了一个奇怪的错误。我设法隔离了问题并生成了一个可复制问题的最小版本。这有一个额外的好处,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(...);
。如果没有向构造函数提供任何参数,请考虑简单地删除大括号,因为对象仍然以这种方式默认构造。