编译器何时可以进行优化会导致我的C ++代码表现出错误的行为,如果没有执行这些优化,那么这些行为将不会出现?例如,在某些情况下不使用volatile
会导致程序行为不正确(例如,不从内存中重新读取变量的值,而只读取一次并将其存储在寄存器中)。但是,在开启最具侵略性的优化标志之前,还有其他陷阱需要知道吗,然后想知道为什么程序不再起作用了吗?
答案 0 :(得分:16)
编译器优化不应该影响程序的可观察行为,因此从理论上讲,您不必担心。实际上,如果你的程序陷入未定义的行为,那么任何事情都可能已经发生,所以如果你的程序在启用优化时中断,你只是暴露了现有的错误 - 这不是优化打破它
一个常见的优化点是返回值优化(RVO)和命名返回值优化(NRVO),它基本上意味着函数的值返回的对象直接在接收它们的对象中构造,而不是复制。这会调整构造函数,复制构造函数和析构函数调用的顺序和数量 - 但通常在正确编写这些函数的情况下,行为中仍然没有可观察到的差异。
答案 1 :(得分:5)
除了你提到的情况之外,多线程代码中的时序可以改变,使得看起来不再有效的代码。局部变量的放置可以变化,使得诸如内存缓冲区溢出之类的有害行为在调试中发生但不是释放,优化或非优化,反之亦然。但所有这些都是已经存在的错误,只是由编译器选项更改暴露出来。
这假设编译器的优化器中没有错误。
答案 2 :(得分:3)
我只是遇到了浮点数学问题。有时,对速度的优化可以稍微改变答案。当然,对于浮点数学,“正确”的定义并不总是很容易想到,所以你必须运行一些测试,看看优化是否正在做你期望的事情。优化不一定会使结果错误,只是不同。
除此之外,我从未见过任何优化会破坏正确的代码。编译器编写者很聪明,知道他们在做什么。
答案 3 :(得分:2)
在声明访问易失性内存位置或IO设备时未能包含volatile关键字是代码中的错误;即使你的代码得到优化,这个bug也很明显。
您的编译器将记录任何“不安全”的优化,它会记录打开和关闭它们的命令行开关和编译指示。不安全的优化通常与浮点数学的假设(舍入,像NAN这样的边缘情况)或其他人已经提到的别名有关。
常量折叠可以创建别名,从而使代码中出现错误。因此,例如,如果您有以下代码:
static char *caBuffer = " ";
...
strcpy(caBuffer,...)
你的代码基本上是一个错误,你在一个常量(字面)上乱涂乱画。没有持续折叠,错误不会真正影响任何事情。但就像你提到的易失性bug一样,当你的编译器折叠常量以节省空间时,你可能会乱写另一个文字,比如:
printf("%s%s%s",cpName," ",cpDescription);
因为编译器可能会在用于初始化caBuffer的文字的最后4个字符处将litef参数指向printf调用。
答案 4 :(得分:2)
由编译器优化引起的错误没有根植于代码中的错误是不可预测的并且难以确定(我在检查编译器在我的代码中优化某个区域时创建的汇编代码时找到了一次) 。常见的情况是,如果优化使您的程序不稳定,它只是揭示了程序中的一个缺陷。
答案 5 :(得分:2)
只要您的代码不依赖于未定义/未指定行为的特定表现形式,并且只要代码的功能是根据C ++程序的可观察行为定义的,那么C ++编译器优化不能可能只破坏代码的功能 :
在较新版本的C ++标准中,权限扩展到涵盖所谓的命名返回值优化(NRVO)中的命名对象。
这是优化可以破坏符合C ++代码功能的唯一方法。如果您的代码以任何其他方式受到优化,则可能是代码中的错误或编译器中的错误。
但是,人们可以争辩说,依赖于这种行为实际上只不过是依赖于未指定的行为的特定表现形式。这是一个有效的参数,可用于支持在上述条件下优化可以永远不会破坏程序功能的断言。 volatile
的原始示例不是有效示例。你基本上是在指责编译器打破从一开始就不存在的保证。如果您的问题应该以特定的方式解释(即随机伪造的不存在的假想保证可能优化器可能会中断),那么可能的答案数量实际上是无限的。这个问题根本没有多大意义。
答案 6 :(得分:1)
我刚刚看到(在C ++ 0x中)允许编译器假设某些类的循环将始终终止(以允许优化)。我现在找不到引用,但是如果我可以跟踪它,我会尝试链接它。这可能会导致可观察到的程序更改。
答案 7 :(得分:1)
假设优化器曾破坏您的代码,请不要这样做。这不是它的目的。如果你确实观察到问题,那么自动考虑无意的UB。
是的,线程可能会对您习惯的假设造成严重破坏。你得不到语言或编译器的帮助,尽管这种情况正在发生变化。你做的是不使用volatile进行小便,你使用一个好的线程库。并且您可以使用其中一个同步原语,只要两个或多个线程都可以触及变量。试图采取捷径或自己优化这是一个穿越地狱的单程票。
答案 8 :(得分:1)
在元级别,如果您的代码使用依赖于基于C ++标准的未定义方面的行为,则符合标准的编译器可以自由地销毁您的C ++代码(正如您所说)。如果你没有符合标准的编译器,那么它也可以做非标准的事情,比如破坏你的代码。
大多数编译器都会发布它们所遵循的C ++标准的子集,因此您始终可以将代码写入该特定标准,并且主要假设您是安全的。但是,如果没有遇到错误,你就无法真正防范编译器中的错误,所以你仍然无法保证任何错误。
答案 9 :(得分:0)
我没有确切的细节(也许其他人可以插入),但我听说过循环展开/优化引起的错误,如果循环计数器变量是char / uint8_t类型(在gcc上下文中,即)。
答案 10 :(得分:0)
严格别名是您可能遇到的gcc问题。根据我的理解,对于某些版本的gcc(gcc 4.4),它会通过优化自动启用。这个网站http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html在解释严格的别名规则方面做得很好。