The as-if rule
Allows any and all code transformations that do not change the observable behavior of the program.
Explanation
The C++ compiler is permitted to perform any changes to the program as long as the following remains true:
(until C++11) | |
1) Accesses (reads and writes) to volatile objects occur strictly according to the semantics of the expressions in which they occur. In particular, they are not reordered
|
(since C++11) |
ON
, the changes to the floating-point environment
-
- the result of any floating-point expression other than cast and assignment may have range and precision of a floating-point type different from the type of the expression (see FLT_EVAL_METHOD
- notwithstanding the above, intermediate results of any floating-point expression may be calculated as if to infinite range and precision (unless #pragma STDC FP_CONTRACT is
OFF
Notes
Because the compiler is (usually) unable to analyze the code of an external library to determine whether it does or does not perform I/O or volatile access, third-party library calls also aren't affected by optimization. However, standard library calls may be replaced by other calls, eliminated, or added to the program during optimization. Statically-linked third-party library code may be subject to link-time optimization.
Programs with undefined behavior, e.g. due to access to an array out of bounds, modification of a const object, evaluation order if (n + 1 < n) abort( ) ; , it is removed entirely by some compilers because signed overflow is undefined behavior
New-expression has another exception from the as-if rule: the compiler may remove calls to the replaceable allocation functions |
(since C++14) |
The count and order of floating-point exceptions can be changed by optimization as long as the state as observed by the next floating-point operation is as if no optimization took place:
#pragma STDC FENV_ACCESS ON for (i = 0; i < n; ++i) x + 1; // x + 1 is dead code, but may raise FP exceptions // (unless the optimizer can prove otherwise). However, executing it n times // will raise the same exception over and over. So this can be optimized to: if (0 < n) x + 1;
Example
int& preinc(int& n) { return ++n; } int add(int n, int m) { return n + m; } // volatile input to prevent constant folding volatile int input = 7; // volatile output to make the result a visible side-effect volatile int result; int main() { int n = input; // using built-in operators would invoke undefined behavior // int m = ++n + ++n; // but using functions makes sure the code executes as-if // the functions were not overlapped int m = add(preinc(n), preinc(n)); result = m; }
Output:
# full code of the main() function as produced by the GCC compiler # x86 (Intel) platform: movl input(%rip), %eax # eax = input leal 3(%rax,%rax), %eax # eax = 3 + eax + eax movl %eax, result(%rip) # result = eax xorl %eax, %eax # eax = 0 (the return value of main()) ret # PowerPC (IBM) platform: lwz 9,LC..1(2) li 3,0 # r3 = 0 (the return value of main()) lwz 11,0(9) # r11 = input; slwi 11,11,1 # r11 = r11 << 1; addi 0,11,3 # r0 = r11 + 3; stw 0,4(9) # result = r0; blr # Sparc (Sun) platform: sethi %hi(result), %g2 sethi %hi(input), %g1 mov 0, %o0 # o0 = 0 (the return value of main) ld [%g1+%lo(input)], %g1 # g1 = input add %g1, %g1, %g1 # g1 = g1 + g1 add %g1, 3, %g1 # g1 = 3 + g1 st %g1, [%g2+%lo(result)] # result = g1 jmp %o7+8 nop # in all cases, the side effects of preinc() were eliminated, and the # entire main() function was reduced to the equivalent of result = 2 * input + 3;
See also
C documentation for as-if rule
|