乘法错误的结果:未定义行为还是编译器bug?

8

背景

在调试一个数值库中的问题时,我能够确定数字错误的第一个出现位置。然而,C++代码本身似乎是正确的。于是我查看了Visual Studio C++编译器生成的汇编代码,并开始怀疑这是一个编译器bug。

代码

我能够在代码的一个高度简化、隔离版本中复现该行为:

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

代码包含一些对必要函数的虚假调用(不幸的是),但期望行为应该还是很清楚的。变量d被赋值为1.0,然后乘以本身。此结果应再次为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"Release\vc142.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"Release\DecimateBug2.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" 实际上从未被存储在内存中,似乎只存在于寄存器中。
这让我强烈怀疑编译器存在漏洞。但是,我想确认一下是否有任何未定义的行为,使错误的汇编得到了证明。

4
就我所知,这段代码中没有任何可疑的地方。就我看来,其行为是明确定义的。对我来说,它看起来像是代码生成错误。 - Igor Tandetnik
1
@VladfromMoscow:当你声明无法重现一个疑似编译器错误时,你可能需要提及你正在使用的编译器版本。 - Andreas Wenzel
@AndreasWenzel 我使用的是 Visual C++ 2019 00435-60000-00000-AA438。 - Vlad from Moscow
@VladfromMoscow 很有趣。我怎么才能得到AA438呢?我现在在AA327,但VS更新程序没有提供任何更新。 - Martin
2
我在 Visual Studio Community 2019,v16.11.8 上尝试了 DecimalBug2.zip 的解决方案,并确认了该 Bug。 - prapin
显示剩余6条评论
1个回答

2
尽管没有人回答,但从评论部分我可以得出以下结论:
  • 没有人在错误重现代码中发现任何未定义的行为。
  • 至少有一些人能够重现不希望出现的行为。

因此,我向 Visual Studio 2019 提交了一个错误报告

Microsoft 团队确认了这个问题。

然而,不幸的是,由于 Visual Studio 2022 看起来没有这个问题,所以 Visual Studio 2019 似乎不会获得修复。显然,最近的版本没有这个特定的 bug 对于 Microsoft 的质量标准已经足够了。

我觉得这很令人失望,因为我认为编译器的正确性是至关重要的,而 Visual Studio 2022 刚刚发布了新功能,因此可能包含新的 bug。因此,没有真正的“稳定版本”(一个是前沿,另一个没有 bug 修复)。但我想我们必须接受这一点或选择一个更稳定的编译器。


你不能为免费的软件提出任何索赔,而微软为你提供了解决方案。那么为什么要担心呢?想想还有多少其他的 bug 等着你去发现,而且它们是没有记录的。 - user1196549
2
@YvesDaoust:“许多其他未记录的错误”-这正是关键。为了修复这些错误,必须有人找到它们。这需要时间和用户的努力(就像我在这个错误报告中所做的那样)。如果您不维护一个仅接收修复而不包含新功能的分支,那么永远不会有真正稳定的版本。当人们发现他们的错误并愿意进行一些分析时,微软已经转向具有全新错误的新版本。 - Martin
@YvesDaoust:关于VC++是免费的事情:我并没有声称什么(尽管相同的编译器在付费版本的VS中使用)。实际上,我对这个特定的错误并不太在意。既然我已经确定了问题,我可以应用一个非常简单的解决方法。我只是提到这些质量标准不会激发信任。这让我普遍担心微软的工具链。 - Martin
你想如何记录未发现的 bug?顺便说一句,我大约20年来都没有在 VS 中遇到代码生成 bug 了。 - user1196549

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接