Divide by zero with loop optimization VC++ 2015
史前史:
我们刚刚将我们的开发环境从 VC 2008 切换到 VC 2015。之后我们发现了问题:尽管在 C 代码中测试了除法器,但程序引发了除数为 0。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #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 编译,在运行时它将除以零。 VC 2008 编译运行正常。
分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ; 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 |
编译器从循环体中取出常量部分
我已经使用了几个编译器选项,例如
问题:
感谢您的报告和小型重现(尤其是来自 Visual Studio 内部的反馈!),它们对隔离问题非常有帮助。
这确实是编译器本身的一个错误,特别是在"不变代码运动"优化过程中的安全检查。我已经对此进行了修复,它应该出现在 VS 15.7 中。
目前,最简单的解决方法是通过 https://docs.microsoft.com/en-us/cpp/preprocessor/optimize 禁用具有此代码模式的函数的优化:
1 2 3 | #pragma optimize("", off ) <Function containing code with this loop> #pragma optimize("", on ) |
不幸的是,这个优化过程直接绑定到优化器,所以除了完全禁用优化器 (-Od) 之外没有编译器选项可以解决这个问题。