Visual Studio 2015 更新3 - C++编译器bug?

36

我们发现一个奇怪的问题,在VS2015 Update3编译器中,代码会无明显原因地被省略掉一部分。

我们发现:

  • 这个问题只出现在VS2015 Update3中(Help|About显示为14.0.25431.01 Update 3,cl.exe版本为19.00.24215.1)
  • 这个问题在VS2015 Update2中没有出现(Help|About显示为14.0.25123.00 Update 2,cl.exe版本为19.00.23918)
  • 这个问题只在开启优化(例如默认的Release配置)时出现
  • 这个问题在x86和x64上都会出现
  • 当将代码片段插入全新的“Win32 Console Application”中时(我是指,不需要任何高级命令行选项),才会出现此问题

我们成功将问题代码最小化到以下代码片段:

#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>

int _tmain(int, _TCHAR*[])
{
    volatile int someVar = 1;

    const int indexOffset = someVar ? 0 : 1;    // Loop omitted
    // const int indexOffset = !someVar;        // Loop omitted
    // const int indexOffset = 0;               // Good
    // const int indexOffset = 1;               // Good
    // const int indexOffset = someVar;         // Good
    // const int indexOffset = someVar + 1;     // Good

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        printf("Test passed\n");
    }

    return 0;
}

对于标有“循环被省略”的那些代码行,编译器会省略整个循环体。为什么?据我所知,并没有涉及到未定义行为。


第一个“循环被省略”的反汇编代码:

int _tmain(int, _TCHAR*[])
{
01151010  push        ebp  
01151011  mov         ebp,esp  
01151013  push        ecx  
    volatile int someVar = 1;
01151014  mov         dword ptr [ebp-4],1  

    const int indexOffset = someVar ? 0 : 1;    // Loop omitted
0115101B  mov         eax,dword ptr [someVar]  
    // const int indexOffset = !someVar;        // Loop omitted
    // const int indexOffset = 0;               // Good
    // const int indexOffset = 1;               // Good
    // const int indexOffset = someVar;         // Good
    // const int indexOffset = someVar + 1;     // Good

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        printf("Test passed\n");
    }

    system("pause");
0115101E  push        offset string "pause" (011520F8h)  
01151023  call        dword ptr [__imp__system (0115205Ch)]  
01151029  add         esp,4  
    return 0;
0115102C  xor         eax,eax  
}
0115102E  mov         esp,ebp  
01151030  pop         ebp  
01151031  ret

测试项目:http://dropmefiles.com/S7mwT


在线尝试!

  • 进入http://webcompiler.cloudapp.net/
  • 将示例代码放到编辑器中
  • 在“附加编译器标志”中添加/O2
  • 勾选“编译后运行可执行文件”

错误报告:https://developercommunity.visualstudio.com/content/problem/71906/compiler-optimization-code-generation-bug.html



你缺少一些包含文件。tchar.hwindows.hcstdio。这绝对是编译器的错误 - 它可以在MSVC x64 19.10.25019(VS15 RTM)和19.11.25325(VS15.3 Preview)上工作。你应该升级你的编译器 - MSVC 19.0已不再受支持。 - tambre
@Codeguard -- 你正在查看的是 Visual Studio 本身的版本,而不是编译器版本。要获取编译器版本,请在命令行中输入 cl /? - PaulMcKenzie
@tambre发布的版本是实际编译器(cl.exe)的版本,而不是IDE的版本。这种信息在未来更为重要,因为使用较新版本的VS的项目可以使用来自其他VS版本的编译器工具链。 - Michael Burr
我认为这是编译器的 bug。我可以在 VS 2015 (19.00.24215.1) 和 VS 2017 (19.10.25019) 两个版本的 64 位环境下重现该问题。 - user2672107
3
很多人声称发现了编译器的错误,但实际上却是他们自己代码中的错误。很让人振奋能看到一个可能是真正的编译器错误! - Mark Ransom
显示剩余23条评论
1个回答

24
是的,这是一个Bug。具体来说,这是VS2015 Update 3引入的新SSA优化器中的Bug。未记录的命令行选项-d2SSAOptimizer-告诉编译器后端使用旧的优化器,从而导致Bug不会出现
顺便提一下,您可以将重现步骤最小化为:
int main()
{
    volatile int someVar = 1;

    const int indexOffset = someVar ? 0 : 1;

    for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i)
    {
        return 0;
    }
    return 1;
}

这将帮助编译器开发人员更快地定位问题。


Codeguard补充(我决定Casey的回答应该是最佳答案): 我收到了微软的回复(博客文章作者Gratian Lup,文章链接Introducing a new, advanced Visual C++ code optimizer):

是的,这确实是SSA优化器本身的一个bug - 通常报告为新优化器中的错误实际上来自于其他部分,有时在20年后才会暴露出来。
这是一个小的优化,尝试删除类似(a-Const1) CMP (a-Const2)的比较,如果没有溢出。问题是你的代码有(1-indexOffset)CMP(2-indexOffset),而减法是不可交换的,当然 - 但优化器代码无视了这一点,处理(1-indexOffset)就像它是(indexOffset-1)一样。
这个问题的修复将在VS2017的下一个更大的更新中发布。在那之前,禁用SSA优化器将是一个不错的解决方法。如果不会使事情变慢,只禁用此函数的优化可能是更好的方法。这可以通过#pragma optimize("", off)来完成: https://msdn.microsoft.com/en-us/library/chh3fb0k.aspx

3
我相信编译器开发人员最终会找到方法来进一步减小自身... :) - Codeguard
4
哇!我很惊讶你能那么快地从微软开发人员那里得到如此详细的答案。问题很好,@Casey 给出了很棒的答案,微软也做出了很好的回应。 - Michael Burr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接