命名空间
变体
操作

如实规则

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

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

内容

[编辑] 解释

C++ 编译器被允许对程序进行任何更改,只要以下条件仍然成立

1) 在每个 序列点,所有 volatile 对象的值是稳定的(以前的求值已完成,新的求值尚未开始)。
(直到 C++11)
1)volatile 对象的访问(读和写)严格按照它们所在的表达式的语义发生。特别是,它们 不会相对于同一线程上的其他 volatile 访问重新排序
(从 C++11 开始)
2) 在程序终止时,写入文件的数据与按编写方式执行程序时完全相同。
3) 发送到交互式设备的提示文本将在程序等待输入之前显示。
4) 如果支持 ISO C pragma #pragma STDC FENV_ACCESS 并且设置为 ON,则对 浮点环境(浮点异常和舍入模式)的更改保证会被浮点算术运算符和函数调用观察到,就好像按编写方式执行一样,除了
  • 任何浮点表达式的结果(除了强制转换和赋值之外)可能具有不同于表达式类型的浮点类型的范围和精度(参见 FLT_EVAL_METHOD),
  • 尽管有上述规定,但任何浮点表达式的中间结果可以被计算为好像具有无限的范围和精度(除非 #pragma STDC FP_CONTRACTOFF)。

[编辑] 注意事项

由于编译器(通常)无法分析外部库的代码以确定它是否执行 I/O 或 volatile 访问,因此第三方库调用也不受优化影响。但是,标准库调用可以在优化期间被替换为其他调用、消除或添加到程序中。静态链接的第三方库代码可能会受到链接时优化的影响。

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

复制省略 是如实规则的一个例外:编译器可以删除对移动和复制构造函数的调用,以及对临时对象的匹配析构函数的调用,即使这些调用具有可观察的副作用。

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

(从 C++14 开始)

浮点异常的计数和顺序可以被优化改变,只要通过下一个浮点操作观察到的状态与没有优化时相同。

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

[编辑] 示例

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;

[编辑] 另请参阅

C 文档 for 如实规则