?编译器错误?变量假设不变

voi*_*oid 7 c++ visual-c++ compiler-bug

Visual Studio 版本:17.7.1 (MSVC 19.37.32822)
使用默认设置和编译器标志新创建的项目。

最小可重现示例:

#include <cstdio>

__declspec(noinline) void test2(char** data)
{
    // After moving the pointer:
    // data_1 now points to data[1] = 1
    // data_0 now points to data[0] = 2
    *data += 1;
}

__declspec(noinline) void test(char* data_1)
{
    char* data_0 = data_1;
    test2(&data_1);
    int len = (int)(data_1 - data_0);

    if (*data_1 & 1)
    {
        if (*data_0 & 2)
            printf("good\n");
     }
}

int main()
{
    char data[2];
    data[0] = 2;
    data[1] = 1;
    test(data);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Release| 中没有打印“good”行 x64 配置。
调试或发布| x86 产生预期结果。

我做了一些实验并查看了生成的程序集,以便将原始代码简化为这个 MRE。
该问题的根本原因似乎是编译器的假设data1test2(). 因此,该行data_0 = data_1可以省略,并且可以在表达式 中data_1使用 not 。 每一行都是必需的,包括转换为int,这可能解释了 x86 构建中缺乏错误再现的原因。data_0(*data_0 & 2)
(data_1 - data_0)

更新1

问题仅发生在 x64 Release 版本中。通过将 /O2 更改为 /Od 或使用#pragma optimize("", off)“修复”问题来禁用优化。

按 VS 版本细分的再现性:

  • 17.7.1 - 问题
  • 17.7.2 - 问题
  • ???
  • 17.7.6 - 没问题
  • 17.8 预览版 7 - 没问题

问题可能已经被解决或者其重现的条件已经改变。

更新2

一位 MSVC 开发人员在一次私人对话中确认了这个问题,我提出这个问题来跟踪进度:https://developercommunity.visualstudio.com/t/Compiler-optimization-bug-in-VS-2022/10512534

Mar*_*own 5

我可以确认这里也存在同样的行为。此外,任何尝试 printf 值来检查 else(失败)条件都会导致正确的行为。优化器过于激进。它只获取 *data_1 并对它应用两个测试(假设 data_1==data_0 这当然不是真的)。

打印调试值的任何更改似乎都会导致正确的行为。我将 printf("bad data_0") 添加到内部 else 子句中,并且可以预见地打印出来了。我更喜欢对所有路径进行注释。

FWIW Intel 编译器 2023 运行良好并打印“good”。

确凿的证据 - MSC 编译器错误过于激进的优化无法加载 *data_0 并将这两个测试应用于 *data_1。反汇编在这里(稍作补充,不会影响错误行为):

--- C:\Users\Martin\source\repos\Toy_bug1\Toy_bug1.cpp ------------------------

// After moving the pointer:
// data_1 now points to data[1] = 1
// data_0 now points to data[0] = 2
*data += 1;
00007FF6D10B1070  inc         qword ptr [rcx]  
}
00007FF6D10B1073  ret  
[snip]
__declspec(noinline) void test(char* data_1)
{
00007FF6D10B1080  mov         qword ptr [rsp+8],rcx  
00007FF6D10B1085  sub         rsp,28h  
char* data_0 = data_1;
test2(&data_1);
00007FF6D10B1089  lea         rcx,[data_1]  
00007FF6D10B108E  call        test2 (07FF6D10B1070h)  
int len = (int)(data_1 - data_0);

if (*data_1 & 1)
00007FF6D10B1093  mov         rax,qword ptr [data_1]  
00007FF6D10B1098  movzx       ecx,byte ptr [rax]  
00007FF6D10B109B  test        cl,1  
00007FF6D10B109E  je          test+3Eh (07FF6D10B10BEh)  
{
    if (*data_0 & 2)
00007FF6D10B10A0  test        cl,2  // this test is incorrect!
        printf("good\n");
    else
        printf("bad d0");
}
00007FF6D10B10A3  lea         rax,[string "bad d0" (07FF6D10B2258h)]  
00007FF6D10B10AA  lea         rcx,[string "good\n" (07FF6D10B2250h)]  
00007FF6D10B10B1  cmove       rcx,rax  
}
Run Code Online (Sandbox Code Playgroud)

我仍然对 MRE 中关键行的重要性感到有点困惑,该行已被优化而不存在,但对于显示故障至关​​重要!即:

int len = (int)(data_1 - data_0);

如果没有此行,MSC 编译器将在 x64 版本中正确运行。以下是在这种情况下生成的正确代码的反汇编:

//    int len = (int)(data_1 - data_0);

if (*data_1 & 1)
00007FF6D6D11096  mov         rax,qword ptr [data_1]  
00007FF6D6D1109B  test        byte ptr [rax],1  
00007FF6D6D1109E  je          test+3Eh (07FF6D6D110BEh)  
{
     if (*data_0 & 2)
00007FF6D6D110A0  test        byte ptr [rdx],2  
// rax  is data_1
// rdx  is data_0
Run Code Online (Sandbox Code Playgroud)

这个故事的寓意是要小心做那些让优化编译器感到困惑的事情!