乘法运算的错误结果:未定义的行为还是编译器错误
Wrong result of multiplication: Undefined behavior or compiler bug?
背景
在调试数字库中的一个问题时,我能够确定数字开始出错的第一个地方。然而,C++代码本身似乎是正确的。因此,我查看了由Visual Studio的C++编译器生成的程序集,并开始怀疑存在编译器错误。
代码
我能够在一个高度简化、孤立的代码版本中重现这种行为:
sourceB.cpp:
double alwaysOneB(double a[3]) {
return 1.0;
}
main.cpp:
#include <iostream>
__declspec(noinline)
bool alwaysTrue() {
return true;
}
__declspec(noinline)
double alwaysOneA(const double a[3]) {
return 1.0;
}
double alwaysOneB(double a[3]); // implemented in sourceB.cpp
int main() {
double* result = new double[2];
if (alwaysTrue()) {
double v[3];
v[0] = 0.0;
v[1] = 0.0;
v[2] = 0.0;
alwaysOneB(v);
double d = alwaysOneA(v); // d = 1
std::cout << "d = " << d << std::endl; // output: "d = 1" (as expected)
result[0] = d * v[2];
result[1] = d * d; // should be: 1 * 1 => 1
}
if (alwaysTrue()) {
std::cout << "result[1] = " << result[1] << std::endl; // output: "result[1] = 2.23943e-47" (expected: 1)
}
delete[] result;
return 0;
}
该代码包含一些对其他函数的伪调用,这些函数(不幸的是(是重现问题所必需的。然而,预期的行为应该仍然非常清楚。将值1.0
分配给变量d
,然后将其自身相乘。这个结果应该是1.0
,它被写入一个数组并打印到控制台。因此,所需的输出是:
d = 1
result[1] = 1
然而,获得的输出是:
d = 1
result[1] = 3.77013e+214
测试环境
该代码使用Visual Studio Community 2019附带的C++编译器进行了测试(最新更新,VS 16.11.9,VC++00435-60000-00000-AA327(。问题仅在激活优化(/O2
(时发生。使用/Od
进行编译会生成一个二进制文件,打印正确的输出。
在简化的例子中(不是针对编译完整库时的原始问题(,我还必须停用";全程序优化">,否则编译器将清除我的伪函数调用。
此简化示例仅在为x86
编译时再现该问题(其他示例再现x64
的问题(。
完整的编译命令行如下所示:/permissive- /ifcOutput "Release" /GS /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Releasevc142.pdb" /Zc:inline /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /FC /Fa"Release" /EHsc /nologo /Fo"Release" /Fp"ReleaseDecimateBug2.pch" /diagnostics:column
要下载的完整Visual Studio解决方案:https://drive.google.com/file/d/1EyoX0uXEkvfJ_Fh649k9XjJQPdDUMik7/view?usp=sharing
GNU编译器和Clang都会生成二进制文件,打印所需的结果。
问题
这段代码中是否有我看不到的未定义行为,从而证明了错误的结果是正确的?或者我应该将其报告为编译器错误吗?
编译器生成的程序集
对于两条乘法线
result[0] = d * v[2];
result[1] = d * d;
编译器生成以下汇编代码:
00CF1432 movsd xmm1,mmword ptr [esp+18h] // Load d into first part of xmm1
00CF1438 unpcklpd xmm1,xmm1 // Load d into second part of xmm1
00CF143C movups xmm0,xmmword ptr [esp+30h] // Load second operands into xmm0
00CF1441 mulpd xmm0,xmm1 // 2 multiplications at one
00CF1445 movups xmmword ptr [esi],xmm0 // store result
显然,它试图使用mulpd
同时执行两次乘法运算。在前两行中,它成功地将d
操作数加载到xmm1
寄存器的两个部分(作为第一个操作数(。但是,当它试图加载第二个操作数(v[2]
和d
(时,它只是从v[2]
地址(esp+30h
(加载128位。这适用于第一次乘法(v[2]
(的第二个操作数,但不适用于第二次乘法(使用d
(。显然,该代码假设d
位于存储器中v
之后。然而,事实并非如此。变量d
实际上从未存储在内存中,它似乎只存在于寄存器中。
这让我强烈怀疑是编译器错误。但是,我想确认,我没有遗漏任何未定义的行为来证明不正确的程序集是正确的。
尽管没有人发布答案,但从评论部分我可以得出结论:
- 没有人在错误修复代码中发现任何未定义的行为
- 至少你们中的一些人能够重现不想要的行为
所以我提交了一份针对Visual Studio 2019的错误报告。
微软团队确认了这个问题。
然而,不幸的是,Visual Studio 2019似乎不会收到错误修复,因为Visual Studio 2022似乎没有错误。显然,对于微软的质量标准来说,最新版本没有那个特定的错误就足够了。
我觉得这很令人失望,因为我认为编译器的正确性至关重要,而Visual Studio 2022刚刚发布了新功能,因此可能包含新的错误。所以没有真正的";稳定版本";(一个是最先进的,另一个没有得到错误修复(。但我想我们必须接受这一点,或者选择一个不同的、更稳定的编译器。
- MSVC多行宏编译器错误
- 静态数据成员的问题-修复链接错误会导致编译器错误
- C++,我收到一个无法理解的编译器错误
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- C++错误C2600:无法定义编译器生成的特殊成员函数(必须首先在类中声明)
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- 重载方法的方式会在使用临时调用时生成编译器错误
- 尝试使用继承和模板实现CRTP.Visual Studio正在生成编译器错误
- 有没有办法让编译器在我放置字符串而不是 nlohmann::json 对象时抛出错误?
- 致命错误 C1001 :vs2017 15.8.4 的内部错误(编译器文件"msc1.cpp",第
- C++2440错误-编译器认为字符串是常量字符
- Clang 错误 – 编译器错误或缺少一些细节?
- 视觉C++ 2017 错误?编译器优化表达式
- 配置:错误:C++编译器无法创建可执行文件
- PHP PDO_ODBC配置:错误:C 编译器无法创建可执行文件
- 致命错误 C1001:内部编译器错误(编译器文件"MSC1.cpp",第 1794 行)
- 错误:编译器限制达到简单hello world程序的堆限制
- c++链接错误:编译器找不到函数的定义
- 生成 CImg 时出现致命错误(编译器限制)
- 抛出对齐类型时出现 Clang 运行时错误.编译器错误