Namespaces
Variants

The as-if rule

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

目录

说明

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

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

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

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

(直至 C++26)

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

对于每个未定义操作 U ,若存在可观察检查点 CP 使得 OP 先于 CP 发生且 CP 先于 U 发生,则操作 OP 无未定义 的。程序在给定输入下的 已定义前缀 包含其所有无未定义操作。

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

如果某些输入将导致 未定义行为 ,编译器无法保证该输入下程序不属于已定义前缀的任何可观察行为。

(自 C++26 起)

注释

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

具有未定义行为的程序在使用不同优化设置重新编译时,常会改变可观察行为。例如,若对带符号整数溢出的检测依赖于该溢出的结果,如 if ( n + 1 < n ) abort ( ) ; 某些编译器会完全移除该检测 ,因为 带符号溢出属于未定义行为 ,优化器可假定其永远不会发生,故而该检测是冗余的。

复制消除 是as-if规则的例外情况:编译器可以移除对移动构造函数和复制构造函数的调用,以及临时对象析构函数的匹配调用,即使这些调用具有可观察的副作用。

new expression 存在另一项对 as-if 规则的例外:编译器可以移除对 可替换分配函数 的调用,即使提供了具有可观测副作用的用户自定义替换函数。

(since 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输入防止常量折叠
volatile int input = 7;
// 使用volatile输出使结果成为可见的副作用
volatile int result;
int main()
{
    int n = input;
// 使用内置运算符会引发未定义行为
//  int m = ++n + ++n;
// 但使用函数可确保代码按非重叠方式执行
    int m = add(preinc(n), preinc(n));
    result = m;
}

输出:

# GCC编译器生成的主函数完整代码
# x86 (Intel) 平台:
        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 (main函数的返回值)
        ret
# PowerPC (IBM) 平台:
        lwz 9,LC..1(2)
        li 3,0          # r3 = 0 (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) 平台:
        sethi   %hi(result), %g2
        sethi   %hi(input), %g1
        mov     0, %o0                 # o0 = 0 (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
# 所有情况下,preinc()的副作用均被消除,
# 整个main()函数被简化为等效的 result = 2 * input + 3;

参见

C 文档 关于 as-if rule