用循环优化VC ++ 2015除以零

Ily*_*nov 5 divide-by-zero compiler-optimization visual-c++ visual-studio-2015 visual-studio-2017

史前史:

我们刚刚将开发环境从VC ++ 2008切换到了VC ++2015。此后,我们发现了一个问题:尽管在C ++代码中测试了除法器,程序仍将除以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);
}
Run Code Online (Sandbox Code Playgroud)

由具有默认发布选项的VC ++ 2015 Update 3或VC ++ 2017编译,运行时它将除以零。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
Run Code Online (Sandbox Code Playgroud)

编译器((8 % test_for_zero) * test_for_zero)从循环体中取出了恒定的部分,只是test_for_zero在除法之前忘记进行测试。显然,仅通过执行编译器工作就可以轻松地将其固定在正确的位置。

我玩过几个编译器选项,例如-d2SSAOptimizer--Oxx,但是解决此问题的唯一选项是-Od

问题:

  1. 是不是错误?自VC 2008以来,C ++标准已发生了重大变化,因此它会受到这种影响吗?
  2. 主要问题是,除了-Ob以外,是否有其他解决方法可通过编译器选项解决此问题?

小智 1

感谢您的报告和小重现(特别是来自 Visual Studio 内部的反馈!),它们对于隔离问题非常有帮助。

这确实是编译器本身的错误,特别是“不变代码运动”优化过程中的安全检查。我已经修复了这个问题,应该会出现在 VS 15.7 中。

目前,最简单的解决方法是通过https://learn.microsoft.com/en-us/cpp/preprocessor/optimize禁用具有此代码模式的函数的优化:

    #pragma optimize( "", off )  
    <Function containing code with this loop>
    #pragma optimize( "", on )  
Run Code Online (Sandbox Code Playgroud)

不幸的是,这个优化过程直接与优化器相关,因此除了完全禁用优化器(-Od)之外没有编译器选项来解决这个问题,正如您所发现的。