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

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:

  • En cada punto de secuencia , los valores de todos los objetos volatile son estables (las evaluaciones previas están completas, las nuevas evaluaciones no han comenzado).
(hasta C++11)
  • Los accesos (lecturas y escrituras) a objetos volatile ocurren estrictamente de acuerdo con la semántica de las expresiones en las que aparecen. En particular, no se reordenan con respecto a otros accesos volatile en el mismo hilo.
(desde C++11)
  • Al finalizar el programa, los datos escritos en archivos son exactamente como si el programa se ejecutara tal como fue escrito.
(until C++26)
  • Los datos entregados al entorno host se escriben en archivos.
(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 OP es libre de indefinición si para cada operación indefinida U , existe un punto de control observable CP tal que OP ocurre antes de CP y CP ocurre antes de U . El prefijo definido del programa con una entrada dada comprende todas sus operaciones libres de indefinició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