constexpr 在联合内初始化结构时,msvc visual c++ 静默不正确的代码生成
msvc visual c++ silent incorrect code generation when constexpr initializing struct within union
考虑以下片段以说明问题:
#include <cstdio>
struct A {
union {
struct { int a0, a1; } aa;
int bb[2];
};
#if 1 //bug
constexpr A(int a0, int a1) : aa{a0, a1} {}
#else //OK
constexpr A(int a0, int a1) : bb{a0, a1} {}
#endif
};
int main() {
A t0{2, 3}; std::printf("%d %d\n", t0.aa.a0, t0.aa.a1); //2 3 (OK)
static A t1{2, 3}; std::printf("%d %d\n", t1.aa.a0, t1.aa.a1); //2 3 (OK)
constexpr A t2{2, 3}; std::printf("%d %d\n", t2.aa.a0, t2.aa.a1); //2 3 (OK)
static constexpr A t3{2, 3}; std::printf("%d %d\n", t3.aa.a0, t3.aa.a1); //0 0 (??)
}
msvc 2015 update 3 和 2017 rc1 通过对 t3 进行零初始化而不是使用给定值正确初始化它来静默生成不正确的代码。 gcc 和 clang 没问题。
我考虑过报告错误,但太麻烦了(我不使用 IDE)。如果您关心,请确认这是一个错误,并告知 Microsoft 的相关人员。
我可以在 VS 2015 上使用 x86-32 和 x86-64 编译器重现这个。
我还可以确认当前版本的 GCC、Clang 和 ICC 可以正确编译它。我也无法在语言标准中找到任何禁止您正在做的事情。该实现在是否在编译时(正如您所期望的那样)或 运行 时初始化对象方面有一些回旋余地,但 MSVC 根本没有初始化它。
正如所料,将对象的声明移至命名空间范围时会出现相同的问题,例如:
#include <cstdio>
struct A {
union {
struct { int a0, a1; } aa;
int bb[2];
};
constexpr A(int a0, int a1) : aa{a0, a1} { }
};
namespace {
constexpr A t3 { 2, 3 };
};
int main() {
std::printf("%d %d\n", t3.aa.a0, t3.aa.a1);
}
…以及对象是否被声明为空结构的静态成员。
虽然可见效果是零初始化,但实际上这并不是编译器正在做的事情。相反,它根本无法在相应的数据段中发出一个值,因此当代码尝试从该地址加载一个值以在调用 printf
时压入堆栈时,它最终压入一个零.
看来这里的union
是重现bug的关键。如果 A
只包含结构 aa
,则没有问题。简单地删除 bb
以留下一个成员联盟也可以解决问题。
请注意,您在这里拥有 submit a bug report to Microsoft for the VS 2017 compiler 所需的一切(不需要使用 IDE)。问题中包含的代码是一个独立的示例,可以很好地演示问题。如果您真的不想自己做,请告诉我,我会提交错误。
考虑以下片段以说明问题:
#include <cstdio>
struct A {
union {
struct { int a0, a1; } aa;
int bb[2];
};
#if 1 //bug
constexpr A(int a0, int a1) : aa{a0, a1} {}
#else //OK
constexpr A(int a0, int a1) : bb{a0, a1} {}
#endif
};
int main() {
A t0{2, 3}; std::printf("%d %d\n", t0.aa.a0, t0.aa.a1); //2 3 (OK)
static A t1{2, 3}; std::printf("%d %d\n", t1.aa.a0, t1.aa.a1); //2 3 (OK)
constexpr A t2{2, 3}; std::printf("%d %d\n", t2.aa.a0, t2.aa.a1); //2 3 (OK)
static constexpr A t3{2, 3}; std::printf("%d %d\n", t3.aa.a0, t3.aa.a1); //0 0 (??)
}
msvc 2015 update 3 和 2017 rc1 通过对 t3 进行零初始化而不是使用给定值正确初始化它来静默生成不正确的代码。 gcc 和 clang 没问题。
我考虑过报告错误,但太麻烦了(我不使用 IDE)。如果您关心,请确认这是一个错误,并告知 Microsoft 的相关人员。
我可以在 VS 2015 上使用 x86-32 和 x86-64 编译器重现这个。
我还可以确认当前版本的 GCC、Clang 和 ICC 可以正确编译它。我也无法在语言标准中找到任何禁止您正在做的事情。该实现在是否在编译时(正如您所期望的那样)或 运行 时初始化对象方面有一些回旋余地,但 MSVC 根本没有初始化它。
正如所料,将对象的声明移至命名空间范围时会出现相同的问题,例如:
#include <cstdio>
struct A {
union {
struct { int a0, a1; } aa;
int bb[2];
};
constexpr A(int a0, int a1) : aa{a0, a1} { }
};
namespace {
constexpr A t3 { 2, 3 };
};
int main() {
std::printf("%d %d\n", t3.aa.a0, t3.aa.a1);
}
…以及对象是否被声明为空结构的静态成员。
虽然可见效果是零初始化,但实际上这并不是编译器正在做的事情。相反,它根本无法在相应的数据段中发出一个值,因此当代码尝试从该地址加载一个值以在调用 printf
时压入堆栈时,它最终压入一个零.
看来这里的union
是重现bug的关键。如果 A
只包含结构 aa
,则没有问题。简单地删除 bb
以留下一个成员联盟也可以解决问题。
请注意,您在这里拥有 submit a bug report to Microsoft for the VS 2017 compiler 所需的一切(不需要使用 IDE)。问题中包含的代码是一个独立的示例,可以很好地演示问题。如果您真的不想自己做,请告诉我,我会提交错误。