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)
  • При завершении программы данные, записанные в файлы, точно соответствуют тому, как если бы программа была выполнена в соответствии с записью.
(until C++26)
  • Данные, переданные в хост-среду, записываются в файлы.
(since C++26)
  • Текст приглашения, который отправляется на интерактивные устройства, будет показан до того, как программа начнет ожидать ввод.
  • Если поддерживается прагма ISO C #pragma STDC FENV_ACCESS и она установлена в ON , изменения в floating-point environment (исключения с плавающей точкой и режимы округления) гарантированно соблюдаются операциями с плавающей точкой и вызовами функций, как если бы они выполнялись в точности как написано, за исключением того, что
    • результат любого выражения с плавающей точкой, кроме приведения и присваивания, может иметь диапазон и точность типа с плавающей точкой, отличного от типа выражения (см. FLT_EVAL_METHOD ),
    • несмотря на вышесказанное, промежуточные результаты любого выражения с плавающей точкой могут вычисляться как с бесконечным диапазоном и точностью (если только #pragma STDC FP_CONTRACT не установлена в OFF ).

Компилятору C++ разрешено вносить любые изменения в программу при условии, что при одинаковых входных данных наблюдаемое поведение программы соответствует одному из возможных наблюдаемых поведений для этих входных данных.

Однако, если определенные входные данные приведут к неопределенному поведению , компилятор не может гарантировать какое-либо наблюдаемое поведение программы с такими входными данными, даже если какая-либо операция наблюдаемого поведения происходит до любой возможной неопределенной операции.

(до C++26)

Программа может содержать наблюдаемые контрольные точки  .

Операция OP является свободной от неопределенности , если для каждой неопределенной операции U существует наблюдаемая контрольная точка CP такая, что OP происходит до CP и CP происходит до U . Определенный префикс программы с заданными входными данными включает все ее операции, свободные от неопределенности.

Компилятору C++ разрешено вносить любые изменения в программу при условии, что при одинаковых входных данных наблюдаемое поведение определенного префикса программы соответствует одному из возможных наблюдаемых поведений для этого определенного префикса.

Если определенные входные данные приведут к неопределенному поведению , компилятор не может гарантировать какое-либо наблюдаемое поведение программы с такими входными данными, которое не принадлежит определенному префиксу.

(начиная с C++26)

Примечания

Поскольку компилятор (обычно) не может проанализировать код внешней библиотеки, чтобы определить, выполняет ли она операции ввода-вывода или доступ к volatile-объектам, вызовы сторонних библиотек также не подвержены влиянию оптимизации. Однако вызовы стандартной библиотеки могут быть заменены другими вызовами, удалены или добавлены в программу в процессе оптимизации. Статически скомпонованный код сторонних библиотек может подвергаться межмодульной оптимизации.

Программы с неопределённым поведением часто меняют наблюдаемое поведение при перекомпиляции с разными настройками оптимизации. Например, если проверка переполнения знакового целого опирается на результат этого переполнения, например if ( n + 1 < n ) abort ( ) ; , она полностью удаляется некоторыми компиляторами , поскольку переполнение знаковых чисел является неопределённым поведением , и оптимизатор может свободно предполагать, что оно никогда не происходит, а проверка избыточна.

Копирующее исключение является исключением из правила "как если бы": компилятор может удалить вызовы перемещающих и копирующих конструкторов и соответствующие вызовы деструкторов временных объектов, даже если эти вызовы имеют наблюдаемые побочные эффекты.

new expression имеет еще одно исключение из правила as-if: компилятор может удалять вызовы replaceable allocation functions даже если предоставлена пользовательская замена и она имеет наблюдаемые побочные эффекты.

(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 input для предотвращения свертки констант
volatile int input = 7;
// volatile output для создания видимого побочного эффекта
volatile int result;
int main()
{
    int n = input;
// использование встроенных операторов вызвало бы неопределенное поведение
//  int m = ++n + ++n;
// но использование функций гарантирует выполнение кода так, как если бы
// функции не перекрывались
    int m = add(preinc(n), preinc(n));
    result = m;
}

Вывод:

# полный код функции main(), сгенерированный компилятором 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