The as-if rule
Permite cualquier y todas las transformaciones de código que no cambien el comportamiento observable del programa.
Contenidos |
Explicación
Comportamiento observable de un programa incluye lo siguiente:
|
(hasta C++11) |
|
(desde C++11) |
|
(until C++26) |
|
(since C++26) |
- El texto de solicitud que se envía a dispositivos interactivos se mostrará antes de que el programa espere la entrada.
-
Si la directiva pragma de ISO C
#pragma STDC FENV_ACCESS
es compatible y está establecida en
ON, los cambios en el entorno de punto flotante (excepciones de punto flotante y modos de redondeo) están garantizados para ser observados por los operadores aritméticos de punto flotante y las llamadas a funciones como si se ejecutaran tal como están escritos, excepto que- el resultado de cualquier expresión de punto flotante, excepto las conversiones y asignaciones, puede tener rango y precisión de un tipo de punto flotante diferente al tipo de la expresión (ver FLT_EVAL_METHOD ),
-
no obstante lo anterior, los resultados intermedios de cualquier expresión de punto flotante pueden calcularse como si tuvieran rango y precisión infinitos (a menos que
#pragma STDC FP_CONTRACT
esté establecido en
OFF).
|
El compilador de C++ tiene permitido realizar cualquier cambio al programa siempre que, dada la misma entrada, el comportamiento observable del programa sea uno de los posibles comportamientos observables correspondientes a esa entrada. Sin embargo, si cierta entrada resulta en comportamiento indefinido , el compilador no puede garantizar ningún comportamiento observable del programa con esa entrada, incluso si cualquier operación del comportamiento observable ocurre antes de cualquier posible operación indefinida. |
(hasta C++26) |
|
Un programa puede contener puntos de control observables .
Una operación
El compilador de C++ tiene permitido realizar cualquier cambio al programa siempre que, dada la misma entrada, el comportamiento observable del prefijo definido del programa sea uno de los posibles comportamientos observables correspondientes a ese prefijo definido. Si cierta entrada resulta en comportamiento indefinido , el compilador no puede garantizar ningún comportamiento observable del programa con esa entrada que no pertenezca al prefijo definido. |
(desde C++26) |
Notas
Debido a que el compilador (generalmente) no puede analizar el código de una biblioteca externa para determinar si realiza o no operaciones de E/S o acceso a volátiles, las llamadas a bibliotecas de terceros tampoco se ven afectadas por la optimización. Sin embargo, las llamadas a la biblioteca estándar pueden ser reemplazadas por otras llamadas, eliminadas o añadidas al programa durante la optimización. El código de bibliotecas de terceros enlazado estáticamente puede estar sujeto a optimización en tiempo de enlace.
Los programas con comportamiento indefinido a menudo cambian su comportamiento observable cuando se recompilan con diferentes configuraciones de optimización. Por ejemplo, si una prueba para desbordamiento de enteros con signo depende del resultado de ese desbordamiento, p.ej. if ( n + 1 < n ) abort ( ) ; , es eliminado completamente por algunos compiladores porque el desbordamiento con signo es comportamiento indefinido y el optimizador puede asumir que nunca ocurre y la prueba es redundante.
Copy elision es una excepción a la regla as-if: el compilador puede eliminar las llamadas a los constructores de movimiento y copia, así como las llamadas correspondientes a los destructores de objetos temporales, incluso si esas llamadas tienen efectos secundarios observables.
|
new expression tiene otra excepción a la regla as-if: el compilador puede eliminar llamadas a las funciones de asignación reemplazables incluso si se proporciona un reemplazo definido por el usuario y tiene efectos secundarios observables. |
(since C++14) |
El recuento y el orden de las excepciones de punto flotante pueden cambiar por optimización siempre que el estado observado por la siguiente operación de punto flotante sea como si no hubiera ocurrido ninguna optimización:
#pragma STDC FENV_ACCESS ON for (i = 0; i < n; ++i) x + 1; // x + 1 es código muerto, pero puede generar excepciones de punto flotante // (a menos que el optimizador pueda demostrar lo contrario). Sin embargo, ejecutarlo n veces // generará la misma excepción repetidamente. Por lo tanto, esto se puede optimizar como: if (0 < n) x + 1;
Ejemplo
int& preinc(int& n) { return ++n; } int add(int n, int m) { return n + m; } // entrada volátil para evitar el plegado de constantes volatile int input = 7; // salida volátil para hacer el resultado un efecto secundario visible volatile int result; int main() { int n = input; // usar operadores incorporados invocaría comportamiento indefinido // int m = ++n + ++n; // pero usar funciones asegura que el código se ejecute como si // las funciones no se solaparan int m = add(preinc(n), preinc(n)); result = m; }
Salida:
# código completo de la función main() producido por el compilador GCC
# plataforma 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 (el valor de retorno de main())
ret
# plataforma PowerPC (IBM):
lwz 9,LC..1(2)
li 3,0 # r3 = 0 (el valor de retorno de 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
# plataforma Sparc (Sun):
sethi %hi(result), %g2
sethi %hi(input), %g1
mov 0, %o0 # o0 = 0 (el valor de retorno de 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
# en todos los casos, los efectos secundarios de preinc() fueron eliminados, y
# la función main() completa fue reducida al equivalente de result = 2 * input + 3;
Véase también
|
Documentación de C
para
regla as-if
|