C++ 优化器错误还是编程错误?

C++ 优化器错误还是编程错误?,c++,visual-c++,optimization,visual-studio-2012,C++,Visual C++,Optimization,Visual Studio 2012,首先:我知道大多数优化bug都是由于编程错误或依赖于一些事实造成的,这些事实可能会根据优化设置(浮点值、多线程问题等)而改变 然而,我遇到了一个很难找到的bug,我有点不确定是否有任何方法可以在不关闭优化的情况下防止此类错误的发生。我错过什么了吗?这真的是一个优化器错误吗?下面是一个简化的示例: struct Data { int a; int b; double c; }; struct Test { void optimizeMe(); Data m_da

首先:我知道大多数优化bug都是由于编程错误或依赖于一些事实造成的,这些事实可能会根据优化设置(浮点值、多线程问题等)而改变

然而,我遇到了一个很难找到的bug,我有点不确定是否有任何方法可以在不关闭优化的情况下防止此类错误的发生。我错过什么了吗?这真的是一个优化器错误吗?下面是一个简化的示例:

struct Data {
  int    a;
  int    b;
  double c;
};

struct Test {
  void optimizeMe();

  Data m_data;
};

void Test::optimizeMe() {
  Data * pData; // Note that this pointer is not initialized!

  bool first = true;

  for (int i = 0; i < 3; ++i) {
    if (first) {
      first = false;

      pData = &m_data;

      pData->a = i * 10;
      pData->b = i * pData->a;
      pData->c = pData->b / 2;
    } else {
      pData->a = ++i;
    } // end if
  } // end for
};

int main(int argc, char *argv[]) {
  Test test;
  test.optimizeMe();
  return 0;
}
如您所见,它将不必要的指针取消引用替换为直接写入成员结构。但是,它在
else
分支中不执行此操作。它还会删除
pData
-分配。由于指针现在仍然是单元化的,程序将在
else
-分支中崩溃

当然,这里有很多可以改进的地方,因此您可能会将其归咎于程序员:

  • 忘记指针,做优化器做的事情-直接使用
    m_数据
  • 将pData初始化为nullptr—这样优化器就知道,如果从未分配指针,
    else
    —分支将失败。至少在我的测试环境中,它似乎解决了这个问题
  • 将指针分配移动到循环前面(使用
    &m_data
    有效地初始化
    pData
    ,然后它也可以作为参考而不是指针(为了更好地测量)。这是有意义的,因为在所有情况下都需要pData,因此没有理由在循环内部这样做
至少可以说,代码显然很臭,我并不是想“责怪”优化器这么做。但我在问:我做错了什么?程序可能很难看,但它是有效的代码

我应该补充一点,我正在使用VS2012与C++/CLI和v110_xp-Toolset。优化设置为/O2。请注意,如果您真的想重现问题(但这不是这个问题的真正重点)您需要处理程序的复杂性。这是一个非常简化的示例,优化器有时不会删除指针分配。将
&m_数据
隐藏在函数后面似乎“有帮助”

编辑:

问:我如何知道编译器正在将其优化为类似于所提供的示例

答:我不太擅长阅读汇编程序,但是我已经看过了,并且做了3个观察,这让我相信它的行为是这样的:

  • 一旦优化开始(添加更多的赋值通常会起作用),指针赋值就没有关联的汇编语句。它也没有被上移到声明中,所以它看起来真的没有初始化(至少对我来说)
  • 在程序崩溃的情况下,调试器跳过赋值语句。在程序运行没有问题的情况下,调试器停止在那里
  • 如果我在调试时观察
    pData
    的内容和
    m_data
    的内容,它清楚地显示
    If
    -分支中的所有赋值对
    m_data
    有影响,
    m_data
    接收到正确的值。指针本身仍然指向它从因此,我必须假设它实际上根本没有使用指针来进行赋值
  • 问:它和i(循环展开)有什么关系吗


    答:不,实际的程序实际上使用do{…}while()循环SQL SELECT resultset,使迭代计数完全特定于运行时,并且不能由编译器预先确定。

    在我看来,这确实是一个错误。优化器可以消除不必要的重定向,但不应消除对
    pData的赋值


    当然,您可以通过在循环之前分配给
    pData
    来解决这个问题(至少在这个简单的示例中)。我认为实际代码中的问题并不容易解决。

    我也投票支持优化器错误,如果它在本例中确实是可复制的。要否决优化器,您可以尝试将
    pData
    声明为
    volatile

    将其减少为一个。@djechlin:受优化影响的错误可能很难减少到很小代码示例。问题明确说明这已经是一个简化的示例。您是否查看了程序集,或者您只是假设优化器删除了
    pData
    分配?是的……您是否验证了这一点?或者您只是猜测?Microsoft state将使用最高警告级别来避免微妙的困难要找到bug,我相信这就是其中之一,init pData to nullptr,问题很可能会消失。实际上,在实际代码中,删除指针作为一个整体是解决方案。代码已经超过15年了,像这样的大多数bug都很容易解决,因为代码只需要一些简单的重构。找到bug是一个完全困难的过程不同的故事。因为我现在相当确定这是一个编译器错误,所以我会接受这个答案。一旦我向microsoft提交了一个错误报告,我会添加这个问题的链接,这样就会有一个真正的可复制的例子。我想这更像是一个C++/CLI问题,普通的编译器并没有出现问题。
    if (first) {
      first = false;
    
      // pData-assignment has been removed!
    
      m_data.a = i * 10;
      m_data.b = i * m_data.a;
      m_data.c = m_data.b / m_data.a;
    } else {
      pData->a = ++i; // This will crash - pData is not set yet. 
    } // end if