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。
该问题的根本原因似乎是编译器的假设data1在test2(). 因此,该行data_0 = data_1可以省略,并且可以在表达式 中data_1使用 not 。
每一行都是必需的,包括转换为int,这可能解释了 x86 构建中缺乏错误再现的原因。data_0(*data_0 & 2)(data_1 - data_0)
问题仅发生在 x64 Release 版本中。通过将 /O2 更改为 /Od 或使用#pragma optimize("", off)“修复”问题来禁用优化。
按 VS 版本细分的再现性:
问题可能已经被解决或者其重现的条件已经改变。
一位 MSVC 开发人员在一次私人对话中确认了这个问题,我提出这个问题来跟踪进度:https://developercommunity.visualstudio.com/t/Compiler-optimization-bug-in-VS-2022/10512534
我可以确认这里也存在同样的行为。此外,任何尝试 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)
这个故事的寓意是要小心做那些让优化编译器感到困惑的事情!