The as-if rule
Позволяет любые и все преобразования кода, которые не изменяют наблюдаемое поведение программы.
Содержание |
Объяснение
Наблюдаемое поведение программы включает следующее:
|
(до C++11) |
|
(начиная с 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) |
|
Программа может содержать наблюдаемые контрольные точки .
Операция
Компилятору 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
|