优化程序错误或编程错误?

时间:2013-04-22 18:05:30

标签: c++ visual-c++ optimization visual-studio-2012

首先:我知道大多数优化错误是由于编程错误或依赖于可能根据优化设置(浮点值,多线程问题......)而改变的事实。

然而,我经历了一个非常难以找到的错误,并且有些不确定是否有任何方法可以防止这种错误发生而不关闭优化。我错过了什么吗?这真的是一个优化器错误吗?这是一个简化的例子:

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;
}

真正的程序当然还有很多工作要做。但这一切都归结为这样一个事实,即不使用直接访问m_data,而是使用(先前已经单元化的)指针。一旦我向if (first)部分添加了足够的语句,优化器似乎就会将代码更改为以下内容:

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

正如您所看到的,它通过直接写入成员结构来替换不必要的指针取消引用。但是它不会在else - 分支中执行此操作。它还会删除pData - 赋值。由于指针现在仍然是单元化的,程序将在else - 分支中崩溃。

当然,这里可以改进各种各样的东西,所以你可能会把它归咎于程序员:

  • 忘记指针并执行优化程序的操作 - 直接使用m_data
  • 将pData初始化为nullptr - 这样,如果永远不会分配指针,优化器就会知道else - 分支将失败。至少它似乎解决了我的测试环境中的问题。
  • 将指针赋值移到循环前面(用pData有效地初始化&m_data,然后它也可以是引用而不是指针(为了测量好)。这是有道理的,因为pData是在所有情况下都需要,所以没有理由在循环中这样做。

代码显然很臭,至少可以说,而且我并没有试图“责怪”优化器这样做。但我问:我做错了什么?该程序可能很难看,但它是有效的代码......

我应该补充一点,我正在使用VS2012和C ++ / CLI以及v110_xp-Toolset。优化设置为/ O2。还请注意,如果你真的想重现这个问题(虽然这不是问题的真正意义),你需要解决程序的复杂性。这是一个非常简化的示例,优化器有时不会删除指针赋值。将&m_data隐藏在函数后面似乎“有所帮助”。

修改

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

答:我不是很擅长阅读汇编程序,但是我已经查看了它并且做了3次观察,这让我相信它的表现就是这样:

  1. 一旦优化开始(添加更多赋值通常就是技巧),指针赋值没有关联的汇编语句。它也没有被提升到声明,所以它似乎真的没有初始化(至少对我而言)。
  2. 如果程序崩溃,调试器会跳过赋值语句。如果程序运行没有问题,调试器就会停在那里。
  3. 如果我在调试时观看pData的内容和m_data的内容,则会清楚地显示if - 分支中的所有作业都会对m_data产生影响并m_data收到正确的值。指针本身仍然指向它从一开始就具有的相同的未初始化值。因此,我必须假设它实际上根本没有使用指针来进行赋值。
  4. 问:是否必须对i做任何事情(循环展开)?

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

2 个答案:

答案 0 :(得分:5)

这对我来说肯定是个错误。优化器可以消除不必要的重定向,但它不应该取消对pData的赋值。

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

答案 1 :(得分:1)

如果在这个例子中它真的可以重现,我也会投票给一个优化器bug。要取代优化程序,您可以尝试将pData声明为volatile