c++ 编译器错误-假设变量不变

fbcarpbf  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(99)

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

字符串

发布中未打印“良好”行|x64配置。

发布者|x86产生预期结果。
我做了一些实验,并查看了生成的汇编,以便将原始代码减少到这个MRE。
问题的根本原因似乎是编译器假设test2()中的data1保持不变。因此,可以省略data_0 = data_1行,并且可以使用data_1代替表达式(*data_0 & 2)中的data_0
每一行都是必要的,包括将(data_1 - data_0)转换为int,这可能解释了在x86构建中缺少bug再现的原因。

更新1

问题仅发生在x64版本中。通过将/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

bihw5rsg

bihw5rsg1#

我可以确认这里存在相同的行为。此外,任何尝试打印值以检查else(fail)条件的行为都是正确的。Optimizer过于激进。它只获取 *data_1并对其应用两个测试(假设data_1==data_0,当然这不是真的)。
任何打印调试值的修改似乎都会导致正确的行为。我在内部的else子句中添加了printf(“bad data_0”),可以预见的是,它会被打印出来。我更喜欢注解所有路径。
FWIW Intel compiler 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  
}

字符串
我仍然对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


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

相关问题