关于visual c:使用循环优化VC 2015除以零

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

编译器从循环体中取出常量部分((8 % test_for_zero) * test_for_zero),而忘记在除法之前测试test_for_zero。显然,只需执行编译器工作,就可以很容易地修复它,但要正确。

我已经使用了几个编译器选项,例如 -d2SSAOptimizer--Oxx,但解决此问题的唯一选项是 -Od

问题:

  • 这是一个错误吗?自 VC 2008 以来,C 标准发生了重大变化,所以它会受到这样的影响吗?
  • 主要问题是,除了-Ob,是否有任何解决方法可以通过编译器选项解决问题?

  • 感谢您的报告和小型重现(尤其是来自 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) 之外没有编译器选项可以解决这个问题。