如同规则

来自cppreference.com
< cpp‎ | language
 
 
C++ 语言
 

允许任何和一切不改变程序可观察行为的代码转换

目录

[编辑] 解释

只要保证以下几点,C++编译器就被允许对程序进行任何修改:

1) 在每一个 序列点, 所有 volatile 对象的值是稳定的(之前的求值已经完成,新的求值没有开始)
(C++11 前)
1)volatile 对象的访问(读或写)严格按照它们所发生的表达式的语义进行。特别地,它们 不会重新排序 那些在同一线程的其它 volatile 访问。
(C++11 起)
2) 程序终止时,写入文件的数据完全如同程序是按照所写的代码那样执行的一般。
3) 发送到交互式设备的提示文本将在程序等待输入之前显示。
4) 如果 #pragma STDC FENV_ACCESS 被设为 ON,对 浮点环境 (浮点异常和舍入模式)的修改保证会被浮点算术运算符以及函数调用所观察,就如同按照所写的代码那样执行一般,除非
  • 除去转型和赋值以外的任何浮点表达式的结果可能有不同于表达式本身的浮点范围和精度(参见 FLT_EVAL_METHOD
  • 尽管如此,任何浮点表达式的中间结果可能按照无限的范围和精度进行计算(除非 #pragma STDC FP_CONTRACTOFF

[编辑] 注释

由于编译器(通常)不能分析一个外部库的代码,以确定它是否执行 I/O 或者 volatile 访问,因此第三方库的调用同样不受这种优化的影响。然而,标准库调用可能会在优化过程中被其它调用替换,消除,或者被添加到程序中。 静态链接的第三方库代码可能会受到链接时优化。

带有 未定义行为 的程序,例如,越界的数组访问,修改常量对象,违反 求值顺序 的规定,等,不受如同规则的限制:当使用不同的优化设置重新编译时,它们常常会表现出不同的可观察行为。例如,如果一个有符号整数溢出的测试依赖于溢出的结果,比如 if(n+1 < n) abort();会被某些编译器完全删除 因为 有符号数溢出是未定义行为 而优化器可以自由地假设它永远不会发生,并且测试是多余的。

复制消除 是如同规则的一个例外:编译器可以删除对移动和复制构造函数的调用以及关联的临时对象的析构函数的调用,哪怕这些调用具有可观察的副作用。

New表达式 具有如同规则的另一项例外:编译器可以删除对 可替换分配函数 的调用,即使提供了一个用户定义的替代函数并且具有可观察的副作用。

(C++14 起)

浮点异常的数目和顺序可以被优化改变,只要状态可以被下一次浮点操作观察到就好像没有优化发生:

#pragma STDC FENV_ACCESS ON
for (i = 0; i < n; i++) x + 1; // x+1 是死代码,但可能会导致浮点异常
// (除非优化器能证否)。然而,执行它 n 次只会反复导致同样的异常。
// 所以这可以被优化为:
if (0 < n) x + 1;

[编辑] 示例

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;
}

输出:

# 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;

[编辑] 另请参阅