史前:
我们刚刚从VC ++ 2008将我们的开发环境转换为VC ++ 2015.在此之后我们发现了问题:尽管在C ++代码中测试了分隔符,程序仍然将0除以0。
测试代码:
#include <cstdlib>
int test(int n, int test_for_zero)
{
int result = 0;
for (int i = 0; i < n; ++i) {
if (test_for_zero)
result += rand() >> ((8 % test_for_zero) * test_for_zero);
}
return result;
}
int main()
{
return test(rand(), rand() & 0x80000000);
}
由VC ++ 2015 Update 3或VC ++ 2017编译并带有默认的Release选项,在运行时它会将除以零。由VC 2008编译运行得很好。
分析:
; 6 : for (int i = 0; i < n; ++i) {
test edi, edi
jle SHORT $LN15@main
; 7 :
; 8 : if (test_for_zero)
; 9 : result += rand() >> ((8 % test_for_zero) * test_for_zero);
mov ecx, DWORD PTR _test_for_zero$1$[ebp]
mov eax, 8
cdq
idiv ecx ; <== IT'S HERE, IDIV BEFORE CHECKING FOR ZERO
mov eax, edx
imul eax, ecx
mov DWORD PTR tv147[ebp], eax
test ecx, ecx
je SHORT $LN15@main
$LL11@main:
call ebx
mov ecx, DWORD PTR tv147[ebp]
sar eax, cl
add esi, eax
sub edi, 1
jne SHORT $LL11@main
编译器从循环体中取出常量部分((8 % test_for_zero) * test_for_zero)
,忘记在除法之前测试test_for_zero
。显然,只需通过编译器作业即可正确地修复它。
我玩了几个编译器选项,例如-d2SSAOptimizer-
和-Oxx
,但解决此问题的唯一方法是-Od
。
问题:
答案 0 :(得分:1)
感谢报告和小型repro(特别是Visual Studio内部的反馈!),它们非常有助于隔离问题。
这确实是编译器本身的一个错误,特别是“不变代码运动”中的安全检查。优化传递。我已经解决了这个问题,它应该出现在VS 15.7中。
目前,最简单的解决方法是通过https://docs.microsoft.com/en-us/cpp/preprocessor/optimize禁用具有此代码模式的函数的优化:
#pragma optimize( "", off )
<Function containing code with this loop>
#pragma optimize( "", on )
不幸的是,这个优化过程直接与优化器相关联,所以除了完全禁用优化器(-Od)之外没有编译器选项可以解决这个问题。