如同规则
允许任何和所有不改变程序可观察行为的代码转换。
目录 |
[编辑] 解释
程序可观察行为包括以下内容
(C++11 前) | |
(自 C++11 起) |
|
(C++26 前) |
|
(自 C++26 起) |
- 发送到交互设备的提示文本将在程序等待输入之前显示。
- 如果 ISO C 编译指示 #pragma STDC FENV_ACCESS 受支持并设置为
ON
,则保证 浮点环境(浮点异常和舍入模式)的更改会被浮点算术运算符和函数调用观察到,如同按编写方式执行一样,但以下情况除外:- 除了类型转换和赋值之外的任何浮点表达式的结果可能具有与表达式类型不同的浮点类型的范围和精度(参见 FLT_EVAL_METHOD),
- 尽管如此,任何浮点表达式的中间结果都可以计算为无限范围和精度(除非 #pragma STDC FP_CONTRACT 为
OFF
)。
C++ 编译器被允许对程序进行任何更改,只要在给定相同输入的情况下,程序的可观察行为是与该输入对应的可能的可观察行为之一。 但是,如果某些输入将导致未定义行为,则编译器不能保证该输入程序的任何可观察行为,即使可观察行为的任何操作发生在任何可能的未定义操作之前。 |
(C++26 前) |
程序可能包含可观察的检查点。 如果对于每个未定义的操作 C++ 编译器被允许对程序进行任何更改,只要在给定相同输入的情况下,程序的已定义前缀的可观察行为是与该已定义前缀对应的可能的可观察行为之一。 如果某些输入将导致未定义行为,则编译器不能保证该输入程序的任何不属于已定义前缀的可观察行为。 |
(自 C++26 起) |
[编辑] 注释
由于编译器(通常)无法分析外部库的代码以确定它是否执行 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 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 文档 关于 如同规则
|