The as-if rule
如果规则
允许不改变程序可观察行为的任何和所有代码转换。
解释
C++编译器允许对程序执行任何更改,只要下列情况仍然正确:
1) At every sequence point, the values of all volatile objects are stable (previous evaluations are complete, new evaluations not started) | (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 with respect to other volatile accesses on the same thread. | (since C++11) |
2%29在程序终止时,写入文件的数据就像程序按写的方式执行一样。
3%、29发送到交互设备的提示文本将在程序等待输入之前显示。
4%29#pragma STDC FENV_ACCESS
设置为ON
,对浮点环境%28浮点异常和舍入模式%29保证由浮点算术运算符和函数调用以书面方式执行,但
- 除强制转换和赋值外的任何浮点表达式的结果可能具有浮点类型的范围和精度,与表达式%28的类型不同(参见
FLT_EVAL_METHOD
%29
- 尽管如此,任何浮点表达式的中间结果都可能被计算成无限范围和精度%28,除非
#pragma STDC FP_CONTRACT
是OFF
%29
注记
由于编译器为%28--通常为%29--无法分析外部库的代码以确定它是否执行I/O或易失性访问,第三方库调用也受到优化的影响。但是,在优化过程中,标准库调用可能会被其他调用所取代、取消或添加到程序中。静态链接的第三方库代码可能受到链接时间优化的影响。
程序未定义行为,例如,由于访问超出界限的数组、修改Const对象,评价顺序违规行为等都不受“如果”规则的影响:当使用不同的优化设置重新编译时,它们经常会改变可观察的行为。例如,如果有符号整数溢出的测试依赖于该溢出的结果,例如if(n+1 < n) abort(,,,它被一些编译器完全删除。因为签名溢出是未定义的行为。而且优化器可以自由地假设它永远不会发生,并且测试是多余的。
复制省略是as-if规则中的一个例外:编译器可以删除对临时对象的析构函数的调用、迁移和复制构造函数的调用,即使这些调用具有明显的副作用。
New-expression has another exception from the as-if rule: the compiler may remove calls to the replaceable allocation functions even if a user-defined replacement is provided and has observable side-effects. | (since 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;
二次
另见
- 复制省略
© cppreference.com
在CreativeCommonsAttribution下授权-ShareAlike未移植许可v3.0。