提问人:void 提问时间:11/10/2023 最后编辑:void 更新时间:11/10/2023 访问量:303
Сompiler 错误?假设变量不变
Сompiler bug? Variable assumed unchanged
问:
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;
}
Release| x64 配置中没有打印“good”行。
调试或发布 |x86 产生预期结果。
我做了一些实验并查看了生成的汇编,以便将原始代码简化为这个 MRE。
问题的根本原因似乎是编译器的假设,该假设在 中保持不变。因此,可以省略该行,并可以使用该行代替在表达式中。
每一行都是必要的,包括 to int 的强制转换,这可能解释了 x86 构建中缺乏错误重现的原因。data1
test2()
data_0 = data_1
data_1
data_0
(*data_0 & 2)
(data_1 - data_0)
更新 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
答:
我可以确认这里存在相同的行为。此外,任何尝试打印值以检查 else(失败)条件都会导致正确的行为。Optimiser 太激进了。它只获取 *data_1 并对其应用两个测试(假设 data_1==data_0这当然不是真的)。
对打印出调试值的任何更改似乎都会导致正确的行为。我将printf(“bad data_0”)添加到内部else子句中,可以预见的是,它被打印出来了。我更喜欢对所有路径进行注释。
FWIW Intel 编译器 2023 运行良好并打印“良好”。
冒烟枪 - 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
故事的寓意是当心做一些混淆优化编译器的事情!
评论
int len = (int)(data_1 - data_0);
start
keypos
评论
data_1
data[1] = 1
data_0
data[0] = 2