简体   繁体   English

什么时候编译器可以优化破坏我的C ++代码?

[英]When can optimizations done by the compiler destroy my C++ code?

When can optimizations done by the compiler cause my C++ code to exhibit wrong behaviour which would not be present had those optimizations not been performed? 什么时候编译器可以进行优化会导致我的C ++代码表现出错误的行为,如果没有执行这些优化,那么这些行为将不会出现? For example, not using volatile in certain circumstances can cause the program to behave incorrectly (eg not re-reading the value of a variable from memory and instead only reads it once and stores it in register). 例如,在某些情况下不使用volatile会导致程序行为不正确(例如,不从内存重新读取变量的值,而只读取一次并将其存储在寄存器中)。 But are there other pitfalls which one should know about before turning on the most aggressive optimization flag and afterwards wondering why the program doesn't work anymore? 但是,在开启最具侵略性的优化标志之前,还有其他陷阱应该知道吗,然后想知道为什么程序不再起作用了?

Compiler optimizations should not affect the observable behavior of your program, so in theory, you don't need to worry. 编译器优化不应该影响程序的可观察行为 ,因此理论上,您不必担心。 In practice, if your program strays in to undefined behavior, anything could already happen, so if your program breaks when you enable optimizations, you've merely exposed existing bugs - it wasn't optimization that broke it. 在实践中,如果在不确定的行为程序流浪狗,什么事情都可能发生已经 ,所以如果你的程序中断时启用优化,你只是暴露现有错误的-这是不优化,打破它。

One common optimization point is the return value optimisation (RVO) and named return value optimization (NRVO) which basically means objects returned by value from functions get constructed directly in the object which is receiving them, rather than making a copy. 一个常见的优化点是返回值优化(RVO)和命名返回值优化(NRVO),它基本上意味着函数的值返回的对象直接在接收它们的对象中构造,而不是复制。 This adjusts the order and number of constructor, copy constructor and destructor calls - but usually with those functions correctly written, there's still no observable difference in the behavior. 这会调整构造函数,复制构造函数和析构函数调用的顺序和数量 - 但通常在正确编写这些函数的情况下,行为中仍然没有可观察到的差异。

Besides the case you mentioned, timing can change in multi-threaded code such that what appears to be working no longer does. 除了你提到的情况,时间可以在多线程代码中改变,这样看起来不再有效。 Placement of local variables can vary such that harmful behaviour like a memory buffer overrun occurs in debug but not release, optimized or non-optimized, or vice versa. 局部变量的放置可以变化,使得诸如内存缓冲区溢出之类的有害行为在调试中发生但不是释放,优化或非优化,反之亦然。 But all of these are bugs that were there already, just exposed by compiler option changes. 但所有这些都是已经存在的错误,只是由编译器选项更改暴露出来。

This is assuming the compiler has no bugs in its optimizer. 这假设编译器在其优化器中没有错误。

I've only run into it with floating point math. 我只是用浮点数学遇到它。 Sometimes the optimizations for speed can change the answer a little. 有时,对速度的优化可以稍微改变答案。 Of course with floating point math, the definition of "right" is not always easy to come up with so you have to run some tests and see if the optimizations are doing what you're expecting. 当然,对于浮点数学,“正确”的定义并不总是很容易想到,所以你必须运行一些测试,看看优化是否正在做你期望的事情。 The optimizations don't necessarily make the result wrong, just different. 优化不一定会使结果错误,只是不同。

Other than that, I've never seen any optimizations break correct code. 除此之外,我从未见过任何优化打破正确的代码。 Compiler writers are pretty smart and know what they're doing. 编译器编写者很聪明,知道他们在做什么。

Failing to include the volatile keyword when declaring access to a volatile memory location or IO device is a bug in your code; 在声明访问易失性内存位置或IO设备时未能包含volatile关键字是代码中的错误; even if the bug is only evident when your code gets optimized. 即使你的代码得到优化,这个bug也很明显。

Your compiler will document any "unsafe" optimizations where it documents the command-line switches and pragmas that turn them on and off. 您的编译器将记录任何“不安全”的优化,它会记录打开和关闭它们的命令行开关和编译指示。 Unsafe optimizations usually related to assumptions about floating point math (rounding, edge cases like NAN) or aliasing as others have already mentioned. 不安全的优化通常与浮点数学的假设(舍入,像NAN这样的边缘情况)或别名已经提到的别名有关。

Constant folding can create aliasing making bugs in your code appear. 常量折叠可以创建别名,从而使代码中出现错误。 So, for example, if you have code like: 因此,例如,如果您有以下代码:

static char *caBuffer = "                                         ";

...

strcpy(caBuffer,...)

Your code is basically an error where you scribble over a constant (literal). 你的代码基本上是一个错误,你在一个常量(字面)上乱涂乱画。 Without constant folding, the error won't really effect anything. 没有持续折叠,错误不会真正影响任何事情。 But much like the volatile bug you mentioned, when your compiler folds constants to save space, you might scribble over another literal like the spaces in: 但就像你提到的易失性bug一样,当你的编译器折叠常量来节省空间时,你可能会乱写另一个文字,比如:

printf("%s%s%s",cpName,"   ",cpDescription);

because the compiler might point the literal argument to the printf call at the last 4 characters of the literal used to initialize caBuffer. 因为编译器可能会在用于初始化caBuffer的文字的最后4个字符处将litef参数指向printf调用。

Bugs caused by compiler optimizations that are not rooted in bugs in your code are not predictable and hard to determine (I managed to find one once when examining the assembly code a compiler had created when optimizing a certain area in my code once). 由编译器优化引起的错误并非根植于代码中的错误,这些错误无法预测且难以确定(在检查编译器在优化代码中的某个区域时创建的汇编代码时,我设法找到一次)。 The common case is that if an optimization makes your program unstable, it just reveals a flaw in your program. 常见的情况是,如果优化使您的程序不稳定,它只是揭示了程序中的一个缺陷。

As long as your code does not rely on specific manifestations of undefined/unspecified behavior, and as long as the functionality of your code is defined in terms of observable behavior of C++ program, a C++ compiler optimizations cannot possibly destroy the functionality of your code with only one exception : 只要你的代码不依赖于未定义/未指定行为的具体表现,只要你的代码的功能在C ++程序的可观察的行为来定义的,C ++编译器的优化不可能摧毁你的代码功能只有一个例外

  • When a temporary object is created with the only purpose of being immediately copied and destroyed, the compiler is allowed to eliminate the creation of such temporary object even if the constructor/destructor of the object has side-effects affecting the observable behavior of the program. 创建临时对象的唯一目的是立即复制和销毁时,即使对象的构造函数/析构函数具有影响程序可观察行为的副作用,也允许编译器消除此类临时对象的创建。

In the newer versions of C++ standard that permission is extended to cover named object in so called Named Return Value Optimization (NRVO). 在较新版本的C ++标准中,权限被扩展为涵盖所谓的命名返回值优化(NRVO)中的命名对象。

That's the only way the optimizations can destroy the functionality of conforming C++ code. 这是优化可以破坏符合C ++代码功能的唯一方法。 If your code suffers from optimizations in any other way, it is either a bug in your code or a bug in the compiler. 如果您的代码以任何其他方式受到优化,那么它可能是代码中的错误或编译器中的错误。

One can argue though, that relying on this behavior is actually nothing else than relying on a specific manifestation of unspecified behavior. 可以说,依赖于这种行为实际上只不过是依赖于未指明行为的特定表现。 This is a valid argument, which can be used to support the assertion that under the above conditions optimizations can never break the functionality of the program. 这是一个有效的参数,可用于支持在上述条件下优化永远不会破坏程序功能的断言。

Your original example with volatile is not a valid example. 您使用volatile原始示例不是有效示例。 You are basically accusing the compiler of breaking guarantees that never existed in the first place. 你基本上是在指责编译器打破从一开始就不存在的保证。 If your question should be interpreted in that specific way (ie what random fake non-existent imaginary guarantees can optimizer possibly break), then the number of possible answers is virtually infinite. 如果你的问题应该以特定的方式解释(即优化器可能会破坏的随机伪造的不存在的假想保证 ),那么可能答案的数量实际上是无限的。 The question simply wouldn't make much sense. 这个问题根本没有多大意义。

I just recently saw that (in C++0x) the compiler is allowed to assume that certain classes of loops will always terminate (to allow optimizations). 我刚刚看到(在C ++ 0x中)允许编译器假设某些类的循环将始终终止(以允许优化)。 I can't find the reference right now but I'll try to link it if I can track it down. 我现在找不到引用,但是如果我可以跟踪它,我会尝试链接它。 This can cause observable program changes. 这可能会导致可观察到的程序更改。

Just don't work from the assumption that the optimizer ever destroys your code. 只是不从优化曾经毁了你的代码的前提工作。 It's just not what it was made to do. 这不是它的目的。 If you do observe problems then automatically consider unintentional UB. 如果你确实观察到问题,那么自动考虑无意的UB。

Yes, threading can play havoc with the kind of assumptions you are used to. 是的,线程可能会对您习惯的假设造成严重破坏。 You get no help from either the language or the compiler, although that's changing. 你得不到语言或编译器的帮助,尽管这种情况正在发生变化。 What you do about that is not piss around with volatile, you use a good threading library. 你对此做了什么并不是易受挥发,你使用一个很好的线程库。 And you use one of its synchronization primitives wherever two or more threads can both touch variables. 并且您可以使用其中一个同步原语,只要两个或多个线程都可以触及变量。 Trying to take short-cuts or optimizing this yourself is a one-way ticket into threading hell. 试图采取捷径或自己优化这是一个穿越地狱的单程票。

At a meta level, if your code uses relies on behavior that is based on undefined aspects of the C++ standard, a standards conforming compiler is free to destroy your C++ code (as you put it). 在元级别,如果您的代码使用依赖于基于C ++标准的未定义方面的行为,则符合标准的编译器可以自由地销毁您的C ++代码(正如您所说)。 If you don't have a standards conforming compiler, then it can also do non-standard things, like destroy your code anyway. 如果你没有符合标准的编译器,那么它也可以做非标准的事情,比如破坏你的代码。

Most compilers publish what subset of the C++ standard they conform to, so you can always write your code to that particular standard and mostly assume you are safe. 大多数编译器都会发布它们所遵循的C ++标准的子集,因此您始终可以将代码写入该特定标准,并且主要假设您是安全的。 However, you can't really guard against bugs in the compiler without having encountered them in the first place, so you still aren't really guaranteed anything. 但是,如果没有遇到它们,你就无法真正防范编译器中的错误,所以你仍然没有真正保证任何东西。

我没有确切的细节(也许其他人可以插入),但是如果循环计数器变量是char / uint8_t类型(在gcc上下文中),我听说过循环展开/优化导致的错误。

Strict aliasing is an issue you might run into with gcc. 严格别名是您可能遇到的gcc问题。 From what I understand, with certain versions of gcc (gcc 4.4) it gets automatically enabled with optimizations. 根据我的理解,对于某些版本的gcc(gcc 4.4),它会通过优化自动启用。 This site http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html does a very good job at explaining strict aliasing rules. 该网站http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html在解释严格的别名规则方面做得非常出色。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM