Spec-Zone.ru › C++

Правило «как будто»

Разрешает любые преобразования кода, не изменяющие наблюдаемого поведения программы.

Объяснение

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

1) В каждой точке последовательности значения всех переменных volatile стабильны (предыдущие вычисления завершены, новые вычисления не начаты). (до C++11)
1) Доступ (чтение и запись) к переменным volatile происходит строго в соответствии с семантикой выражений, в которых они встречаются. В частности, они не переупорядочиваются относительно других обращений к volatile в рамках одного потока. (с C++11)
2) При завершении программы данные, записанные в файлы, будут точно такими же, как если бы программа выполнялась без оптимизации.
3) Текст запроса, отправленный на интерактивные устройства, будет отображён до того, как программа ожидает ввода.
4) Если поддерживается ISO C pragma #pragma STDC FENV_ACCESS и имеет значение ON, изменения в окружении с плавающей точкой (исключения с плавающей точкой и режимы округления) гарантированно будут наблюдаться операторами и функциями арифметики с плавающей точкой, как если бы они выполнялись без оптимизации, за исключением:
  • результат любого выражения с плавающей точкой, кроме приведения типов и присваивания, может иметь диапазон и точность типа с плавающей точкой, отличные от типа выражения (см. FLT_EVAL_METHOD),
  • несмотря на вышесказанное, промежуточные результаты любого выражения с плавающей точкой могут быть рассчитаны как имеющие бесконечный диапазон и точность (если #pragma STDC FP_CONTRACT не OFF).

Примечания

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

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

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

Оператор 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 по правилу «как будто»

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

Spec-Zone.ru

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