Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can compiler optimizations affect code logic?

When the same piece of code is compiled with optimizations fully disabled (g++ -O0) and then again with the optimizations fully enabled (g++ -O3), how can the logic of the source code itself be changed?

For example, compilers can

  • unwind loops and
  • do constant folding.

These two optimizations make the code perform faster without affecting the integrity of the original source code. Any code that runs without these optimizations will run with them enabled.

But, compiler optimizations can also affect code logic. Here are two examples that I know of:

  • Removing copy constructors and assignment operators from temporaries may remove possible side-effects.
  • Rearranging arithmetic containing floating point values may affect floating point error (hopefully requires -ffast-math argument).

I was very surprised, and lucky, to learn about these because they could become huge potential gotchas in the wrong situation.

So I want to know, are there any other cases where c++ compiler optimizations will affect code logic? I'm specifically looking for information about c++11 (without any undefined behaviour) under the g++ compiler, but tips for other compilers are welcome.

like image 915
Ryan Avatar asked Oct 18 '12 02:10

Ryan


2 Answers

The "as-if" Rule:

An implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. For instance, an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced.

However, the standard mentions one optimization which is allowed, and which breaks the "as-if" rule:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.123 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object

— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

— when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.

like image 154
David Avatar answered Sep 28 '22 08:09

David


"Unspecified Behavior" is behavior where the implementation is free to choose any of the possible behaviors. In such cases, an optimizer may affect the choice made.

A simple example is the order in which arguments to a function are evaluated. Non-optimized builds could use Left-to-Right or Right-to-Left, whereas optimized builds may evaluate arguments in a "mixed" order. A good reason would be to maximize the opportunity for Common Subexpression Optimization amongst arguments.

If any of these arguments has noticeable side effects, the code logic would be altered, but whether that's a bug varies from case to case.

like image 37
MSalters Avatar answered Sep 28 '22 10:09

MSalters