Espacios de nombres
Variantes
Acciones

Comportamiento indeterminado

De cppreference.com
< cpp‎ | language

La violación de ciertas reglas del lenguaje hace que el programa no tenga sentido.

Contenido

[editar] Explicación

El estándar de C ++ define con precisión el comportamiento observable de cada programa de C ++ que no pertenece a una de las siguientes clases:

  • mal formado – el programa tiene errores de sintaxis y semánticos detectables. Se requiere un compilador conforme para realizar un diagnóstico, incluso si se define una extensión del lenguaje que asigna significado a dicho código (como las matrices de longitud variable). El texto del estándar usa deberá, no deberá, y mal formado para indicar estos requisitos.
  • mal formado no se requiere diagnóstico – el programa tiene errores semánticos que no son diagnosticables en el caso general (por ejemplo, violaciones de la regla ODR u otros errores que sólo se pueden detectar en la fase de enlazado). El comportamiento es indeterminado si se ejecuta el programa.
  • comportamiento definido por la implementación – el comportamiento del programa varia entre distintas implementaciones, y la implementación conforme debe documentar los efectos de cada comportamiento. Por ejemplo, el tipo std::size_t o el número de bits en un byte, o el texto de std::bad_alloc::what. Un subconjunto del comportamiento definido por la implementación es específicamente local, que depende del locale proporcionado por la implementación.
  • comportamiento inespecífico - el comportamiento del programa cambia entre implementaciones y no es necesario documentar los efectos de cada comportamiento. Por ejemplo, orden de evaluación, si los literales de cadena idénticos son distintos, la cantidad de sobredimensionamiento en asignación de matrices, etc. Cada comportamiento resulta en uno de los resultados válidos del conjunto.
  • comportamiento indeterminado – no hay restricciones en el comportamiento del programa. Ejemplos son: acceso a memoria fuera de los límites de la matriz, desborde de enteros con signo, desreferencia a punto nulo, modificar el mismo escalar más de una vez en una expresión sin puntos de secuencia, acceder a un objeto con un puntero de tipo diferente, etc. No se requiere que los compiladores diagnostiquen el comportamiento indeterminado (aunque se diagnostican muchas situaciones simples), y el programa compilado no está obligado a hacer nada significativo.

[editar] Optimización y comportamiento indeterminado

Debido a que los programas correctos en C++ no tienen un comportamiento indeterminado, los compiladores pueden producir resultados inesperados cuando se compila con la optimización habilitada un programa que realmente tiene un comportamiento indeterminado.

Por ejemplo,

[editar] Desbordamiento de signo

int foo(int x) {
    return x+1 > x; // puede ser verdadero o indeterminado debido al desbordamiento de signo
}

puede compilarse como (demo)

foo(int):
        movl    $1, %eax
        ret

[editar] Acceso fuera de los límites

int table[4] = {};
bool exists_in_table(int v)
{
    // devuelve verdadero en una de las 4 primeras iteraciones o indeterminado por un acceso fuera
    // de los límites
    for (int i = 0; i <= 4; i++) {
        if (table[i] == v) return true;
    }
    return false;
}

puede compilarse como (demo)

exists_in_table(int):
        movl    $1, %eax
        ret

[editar] Escalar sin inicializar

std::size_t f(int x)
{
    std::size_t a;
    if(x) // x debe ser distinto de cero o el comportamiento es indeterminado
        a = 42;
    return a; 
}

se puede compilar como (demo)

f(int):
        mov     eax, 42
        ret

el resultado que se muestra se observó en una versión antigua de gcc

bool p; // variable local sin inicializar
if(p) // comportamiento indeterminado, acceso a escalar sin inicializar
    std::puts("p es verdadero");
if(!p) // UB comportamiento indeterminado, acceso a escalar sin inicializar
    std::puts("p es falso");

Posible salida:

p es verdadero
p es falso

[editar] Desreferencia (indirección) de puntero nulo

int foo(int* p) {
    int x = *p;
    if(!p) return x; // o indeterminado arriba o esta rama nunca se toma
    else return 0;
}
int bar() {
    int* p = nullptr;
    return *p;        // Comportamiento indeterminado incondicional
}

se puede compilar como (foo con gcc, bar con clang)

foo(int*):
        xorl    %eax, %eax
        ret
bar():
        retq

[editar] Acceso a puntero pasado a realloc

Seleccione clang para ver el resultado de salida

#include <iostream>
#include <cstdlib>
int main() {
    int *p = (int*)std::malloc(sizeof(int));
    int *q = (int*)std::realloc(p, sizeof(int));
    *p = 1; // Comportamiento indeterminado de acceso a puntero que se paso a realloc
    *q = 2;
    if (p == q) // Comportamiento indeterminado de acceso a puntero que se paso a realloc
        std::cout << *p << *q << '\n';
}

Posible salida:

12

[editar] Bucle infinito sin efectos secundarios

Seleccione clang para ver el resultado de salida

#include <iostream>
 
int fermat() {
  const int MAX = 1000;
  int a=1,b=1,c=1;
  // Bucle infinito sin efectos secundarios en un comportamiento indeterminado
  while (1) {
    if (((a*a*a) == ((b*b*b)+(c*c*c)))) return 1;
    a++;
    if (a>MAX) { a=1; b++; }
    if (b>MAX) { b=1; c++; }
    if (c>MAX) { c=1;}
  }
  return 0;
}
 
int main() {
  if (fermat())
    std::cout << "Fermat's Last Theorem has been disproved.\n";
  else
    std::cout << "Fermat's Last Theorem has not been disproved.\n";
}

Posible salida:

Fermat's Last Theorem has been disproved.

[editar] Enlaces externos

[editar] Ver también

Documentación de C de Comportamiento indeterminado