编译器优化什么时候会导致我的C++代码表现出错误的行为,如果没有进行这些优化,这些行为将不会出现?例如,在某些情况下不使用volatile
可能会导致程序行为不正确(例如,不从内存重新读取变量的值,而是仅读取一次并将其存储在寄存器中)。但是,在开启最激进的优化标志之前是否还有其他需要知道的陷阱,然后想知道为什么程序不再工作?
编译器优化什么时候会导致我的C++代码表现出错误的行为,如果没有进行这些优化,这些行为将不会出现?例如,在某些情况下不使用volatile
可能会导致程序行为不正确(例如,不从内存重新读取变量的值,而是仅读取一次并将其存储在寄存器中)。但是,在开启最激进的优化标志之前是否还有其他需要知道的陷阱,然后想知道为什么程序不再工作?
编译器的优化不应该影响程序的可观察行为,因此理论上你不需要担心。实际上,如果程序进入了未定义行为,任何事情都可能已经发生了,因此,如果启用优化后程序出现错误,那么你只是暴露了已经存在的错误——并不是因为优化而导致程序错误。
一个常见的优化点是返回值优化(RVO)和命名返回值优化(NRVO),这基本上意味着从函数返回的对象直接在接收它们的对象中构造,而不是复制。这调整了构造函数、复制构造函数和析构函数调用的顺序和数量——但通常情况下,只要这些函数正确地编写了,程序的行为仍然没有可观察的差异。
我只遇到过浮点数运算时出现的问题。有时,为了提高速度进行的优化会稍微改变答案。当然,在浮点数运算中,“正确”的定义并不总是容易得出的,因此您需要运行一些测试来查看优化是否按照您的预期进行。这些优化不一定会使结果错误,只是不同而已。
除此之外,我从未见过任何优化破坏正确代码的情况。编译器编写者非常聪明,知道自己在做什么。
不要以为优化器会破坏你的代码,这不是它的作用。如果你发现问题,请自动考虑无意中的UB。
是的,线程可能会对你习惯的假设造成混乱。你既没有语言也没有编译器的帮助,尽管情况正在改变。你应该使用一个好的线程库来处理这个问题,而不是浪费时间在volatile上。在两个或更多线程可以同时访问变量的地方,使用线程库的同步原语。试图走捷径或自己进行优化将是进入线程地狱的单程票。
volatile
关键字在我问了那些问题之后将会一直困扰我。^^ 一个更好的名称应该是violated
,因为我使用它后感觉就像被违反了一样。 - gablinstatic char *caBuffer = " ";
...
strcpy(caBuffer,...)
你的代码基本上是一种错误,即你在常量(字面值)上涂写。如果没有常量折叠,这个错误实际上不会影响任何东西。但就像你提到的易失性错误一样,当编译器折叠常量以节省空间时,你可能会涂写到另一个字面值中,比如:
printf("%s%s%s",cpName," ",cpDescription);
因为编译器可能会将传递给printf调用的文字参数指向用于初始化caBuffer的文字结尾的最后4个字符。
在较新版本的C++标准中,该许可已扩展到名为命名返回值优化(NRVO)的命名对象。
这是优化可能破坏符合C++代码功能的唯一方式。如果您的代码以任何其他方式受到优化的影响,则它可能是代码中的错误或编译器中的错误。
尽管可以争论说,依赖此行为实际上等同于依赖于未指定的行为的特定表现形式。这是一个有效的论点,可用于支持上述条件下的优化永远不能破坏程序功能的断言。
您最初的带volatile
的示例不是一个有效的示例。您实际上是在指责编译器打破了根本不存在的保证。如果您的问题应以特定方式被解释(即可能优化器可能破坏的随机虚假不存在的想象保证),则可能的答案数量几乎是无限的。这个问题根本没有多大意义。
我最近看到(在C++0x中),编译器允许假设某些类别的循环将始终终止(以允许优化)。我现在找不到参考资料,但如果我能找到,我会尝试链接它。这可能会导致可观察的程序更改。
严格别名是您可能在使用gcc时遇到的问题。据我所知,对于某些版本的gcc(gcc 4.4),它会随着优化自动启用。这个网站http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html非常好地解释了严格别名规则。
在元级别上,如果您的代码依赖于基于C++标准未定义方面的行为,符合标准的编译器可以自由地破坏您的C++代码。如果您没有符合标准的编译器,则它也可以做一些非标准的事情,比如无论如何破坏您的代码。
大多数编译器都会发布其符合的C++标准子集,因此您始终可以将代码编写为特定标准,并且大多数情况下可以假设您是安全的。但是,如果没有先遇到编译器中的错误,您实际上仍然无法防范编译器中的错误,因此您仍然没有真正的保障。