Undefined behavior
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.
- comportamiento específico de la configuración regional - comportamiento definido por la implementación que depende de la configuración regional actualmente seleccionada . Por ejemplo, si islower retorna verdadero para cualquier carácter diferente de las 26 letras latinas minúsculas.
(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
Puede producir la siguiente salida (observado con una versión anterior de gcc):
p es true p es false
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
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) |