命名空间
变体
操作

如同规则

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++17* 前已弃用)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间说明符
初始化
 
 

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

目录

[编辑] 解释

程序可观察行为包括以下内容

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

C++ 编译器被允许对程序进行任何更改,只要在给定相同输入的情况下,程序的可观察行为是与该输入对应的可能的可观察行为之一。

但是,如果某些输入将导致未定义行为,则编译器不能保证该输入程序的任何可观察行为,即使可观察行为的任何操作发生在任何可能的未定义操作之前。

(C++26 前)

程序可能包含可观察的检查点

如果对于每个未定义的操作 U,都存在一个可观察的检查点 CP,使得 OP 发生在 CP 之前,并且 CP 发生在 U 之前,则操作 OP无未定义的。给定输入的程序的已定义前缀包括其所有无未定义的操作。

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 文档 关于 如同规则