Spec-Zone.ru › C++

Правило as-if

Допускает любые преобразования кода, которые не изменяют наблюдаемое поведение программы.

Пояснение

Наблюдаемое поведение программы включает следующее:

  • В каждой точке следования значения всех 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)

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

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

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

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

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

Примечания

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

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

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

Выражение new имеет ещё одно исключение из правила as-if: компилятор может удалять вызовы заменяемых функций выделения памяти, даже если предоставлена пользовательская замена и она имеет наблюдаемые побочные эффекты.

(начиная с 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 для правила as-if

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/as_if

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API