Namespaces
Variants

Undefined behavior

From cppreference.net

El estándar del lenguaje C especifica con precisión el comportamiento observable de los programas en lenguaje C, excepto para aquellos en las siguientes categorías:

  • comportamiento indefinido - no existen restricciones sobre el comportamiento del programa. Ejemplos de comportamiento indefinido son accesos a memoria fuera de los límites de un array, desbordamiento de enteros con signo, desreferenciación de puntero nulo, modificación del mismo escalar más de una vez en una expresión sin puntos de secuencia, acceso a un objeto a través de un puntero de tipo diferente, etc. Los compiladores no están obligados a diagnosticar comportamiento indefinido (aunque muchas situaciones simples son diagnosticadas), y el programa compilado no está obligado a hacer nada significativo.
  • comportamiento no especificado - se permiten dos o más comportamientos y la implementación no está obligada a documentar los efectos de cada comportamiento. Por ejemplo, orden de evaluación , si los literales de cadena idénticos son distintos, etc. Cada comportamiento no especificado resulta en uno de un conjunto de resultados válidos y puede producir un resultado diferente cuando se repite en el mismo programa.
  • comportamiento definido por la implementación - comportamiento no especificado donde cada implementación documenta cómo se realiza la elección. Por ejemplo, número de bits en un byte, o si el desplazamiento a la derecha de enteros con signo es aritmético o lógico.

(Nota: Los programas estrictamente conformes no dependen de ningún comportamiento no especificado, indefinido o definido por la implementación)

Los compiladores deben emitir mensajes de diagnóstico (ya sean errores o advertencias) para cualquier programa que viole cualquier regla de sintaxis de C o restricción semántica, incluso si su comportamiento se especifica como undefined o implementation-defined o si el compilador proporciona una extensión de lenguaje que le permite aceptar dicho programa. No se requieren diagnósticos adicionales para undefined behavior.

Contenidos

UB y optimización

Debido a que los programas C correctos están libres de comportamiento indefinido, los compiladores pueden producir resultados inesperados cuando un programa que realmente tiene UB se compila con optimizaciones habilitadas:

Por ejemplo,

Desbordamiento con signo

int foo(int x)
{
    return x + 1 > x; // verdadero o comportamiento indefinido por desbordamiento con signo
}

puede compilarse como ( demo )

foo:
        mov     eax, 1
        ret

Acceso fuera de límites

int table[4] = {0};
int exists_in_table(int v)
{
    // devuelve 1 en una de las primeras 4 iteraciones o UB debido a acceso fuera de límites
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return 1;
    return 0;
}

Puede compilarse como ( demo )

exists_in_table:
        mov     eax, 1
        ret

Escalar no inicializado

_Bool p; // variable local no inicializada
if (p) // acceso UB a escalar no inicializado
    puts("p is true");
if (!p) // acceso UB a escalar no inicializado
    puts("p is false");

Puede producir la siguiente salida (observado con una versión anterior de gcc):

p es true
p es false
size_t f(int x)
{
    size_t a;
    if (x) // ya sea x distinto de cero o comportamiento indefinido
        a = 42;
    return a;
}

Puede compilarse como ( demo )

f:
        mov     eax, 42
        ret

Escalar no válido

int f(void)
{
    _Bool b = 0;
    unsigned char* p = (unsigned char*)&b;
    *p = 10;
    // leer desde b ahora es UB
    return b == 0;
}

Puede compilarse como ( demo )

f:
        mov     eax, 11
        ret

Desreferencia de puntero nulo

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // O bien hay UB arriba o esta rama nunca se ejecuta
    else
        return 0;
}
int bar()
{
    int* p = NULL;
    return *p;    // UB incondicional
}

puede compilarse como ( demo )

foo:
        xor     eax, eax
        ret
bar:
        ret

Acceso al puntero pasado a realloc

Seleccione clang para observar la salida mostrada

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    *p = 1; // UB access to a pointer that was passed to realloc
    *q = 2;
    if (p == q) // UB access to a pointer that was passed to realloc
        printf("%d%d\n", *p, *q);
}

Salida posible:

12

Bucle infinito sin efectos secundarios

Elija clang para observar la salida mostrada

#include <stdio.h>
int fermat()
{
    const int MAX = 1000;
    // Endless loop with no side effects is UB
    for (int a = 1, b = 1, c = 1; 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(void)
{
    if (fermat())
        puts("Fermat's Last Theorem has been disproved.");
    else
        puts("Fermat's Last Theorem has not been disproved.");
}

Salida posible:

Fermat's Last Theorem has been disproved.

Referencias

  • Estándar C23 (ISO/IEC 9899:2024):
  • 3.4 Comportamiento (p: TBD)
  • 4 Conformidad (p: TBD)
  • Estándar C17 (ISO/IEC 9899:2018):
  • 3.4 Comportamiento (p: 3-4)
  • 4 Conformidad (p: 8)
  • Estándar C11 (ISO/IEC 9899:2011):
  • 3.4 Comportamiento (p: 3-4)
  • 4/2 Comportamiento indefinido (p: 8)
  • Estándar C99 (ISO/IEC 9899:1999):
  • 3.4 Comportamiento (p: 3-4)
  • 4/2 Comportamiento indefinido (p: 7)
  • Estándar C89/C90 (ISO/IEC 9899:1990):
  • 1.6 DEFINICIONES DE TÉRMINOS

Véase también

Documentación de C++ para Comportamiento indefinido

Enlaces externos

1. Lo Que Todo Programador de C Debe Saber Sobre Comportamiento Indefinido #1/3
2. Lo Que Todo Programador de C Debe Saber Sobre Comportamiento Indefinido #2/3
3. Lo Que Todo Programador de C Debe Saber Sobre Comportamiento Indefinido #3/3
4. El comportamiento indefinido puede resultar en viajes en el tiempo (entre otras cosas, pero los viajes en el tiempo son los más extraños)
5. Entendiendo el Desbordamiento de Enteros en C/C++
6. Comportamiento Indefinido y el Último Teorema de Fermat
7. Diversión con Punteros NULL, parte 1 (explotación local en Linux 2.6.30 causada por comportamiento indefinido debido a desreferencia de puntero nulo)