然而,我经历了一个非常难以找到的错误,并且有点不确定是否有任何方法可以防止这种错误发生而不关闭优化.我错过了什么吗?这真的是一个优化器错误吗?这是一个简化的例子:
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)-part添加了足够的语句,优化器似乎就会将代码更改为以下行:
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-branch中没有这样做.它还会删除pData赋值.由于指针现在仍然是单元化的,程序将在else分支中崩溃.
当然,这里可以改进各种各样的东西,所以你可能会把它归咎于程序员:
>忘掉指针并执行优化器的工作 – 直接使用m_data.
>将pData初始化为nullptr – 这样,如果从未分配指针,优化器就会知道else分支将失败.至少它似乎解决了我的测试环境中的问题.
>将指针赋值移到循环前面(用& m_data有效地初始化pData,然后也可以是引用而不是指针(为了好的措施).这是有道理的,因为在所有情况下都需要pData所以没有在循环中做这个的原因.
至少可以说,代码显然很臭,而且我并没有试图“责怪”优化器这样做.但我在问:我做错了什么?该程序可能很丑,但它是有效的代码……
我应该补充一点,我正在使用VS2012和C/C++LI以及v110_xp-Toolset.优化设置为/ O2.还请注意,如果你真的想重现这个问题(虽然这不是这个问题的真正意义),你需要解决程序的复杂性.这是一个非常简化的示例,优化器有时不会删除指针赋值.在函数后面隐藏& m_data似乎“有所帮助”.
编辑:
问:我怎么知道编译器正在将它优化为类似提供的示例?
答:我不太擅长阅读汇编程序,但是我已经查看了它并且做了3次观察,这让我相信它的行为方式如下:
>一旦优化开始(添加更多的赋值通常就是技巧),指针赋值没有关联的汇编语句.它也没有被提升到声明,所以它似乎真的没有初始化(至少对我而言).
>如果程序崩溃,调试器将跳过赋值语句.如果程序运行没有问题,调试器就会停在那里.
>如果我在调试时观察pData的内容和m_data的内容,它清楚地表明if-branch中的所有赋值都对m_data有影响,而m_data会收到正确的值.指针本身仍然指向它从一开始就具有的相同的未初始化值.因此,我必须假设它实际上根本没有使用指针来进行赋值.
问:是否必须对i做任何事情(循环展开)?
答:不,实际程序实际上使用do {…} while()来循环sql SELECT结果集,因此迭代计数完全是运行时特定的,并且不能由编译器预先确定.