Comportamiento no definido
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 los arrays 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 no está definido si se ejecuta tal programa.
- Comportamiento definido por la implementación. El comportamiento del programa varía 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 comportamiento específico de la configuración regional, que depende de la configuración regional proporcionada por la implementación.
- Comportamiento no especificado. El comportamiento del programa cambia entre implementaciones y no es necesario que una implementación conforme documente los efectos de cada comportamiento. Por ejemplo, el orden de evaluación, si los literales de cadena idénticos son distintos, la cantidad del sobredimensionamiento en la asignación de memoria de arrays, etc. Cada comportamiento resulta en uno de los resultados válidos de un conjunto.
- Comportamiento no definido. No hay restricciones en el comportamiento del programa. Ejemplos son: carreras de datos, acceso a memoria fuera de los límites del array, desbordamiento de enteros con signo, desreferencia de un puntero nulo, modificaciones al mismo escalar más de una vez en una expresión sin ningún punto de secuencia intermedio(hasta C++11)que está sin secuenciar(desde C++11), acceso a un objeto a través de un puntero de tipo diferente, etc. No se requiere que los compiladores diagnostiquen el comportamiento no definido (aunque se diagnostican muchas situaciones simples), y el programa compilado no está obligado a hacer nada significativo.
[editar]Optimización y comportamiento no definido
Debido a que los programas C++ correctos están libres de un comportamiento no definido, los compiladores pueden producir resultados inesperados cuando un programa que realmente tiene un comportamiento no definido se compila con la optimización habilitada:
Por ejemplo,
[editar]Desbordamiento de signo
int foo(int x){return x+1> x;// puede ser verdadero o no definido// 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 no definido por un acceso fuera de los límitesfor(int i =0; i <=4; i++){if(table[i]== v)returntrue;}returnfalse;}
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 no está definido 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
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 no definido arriba o esta rama nunca se tomaelsereturn0;}int bar(){int* p = nullptr;return*p;// Comportamiento no definido 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 no definido de acceso a puntero que se paso a realloc*q =2;if(p == q)// Comportamiento no definido de acceso a puntero que se paso a reallocstd::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(){constint MAX =1000;int a=1,b=1,c=1;// Bucle infinito sin efectos secundarios en un comportamiento no definidowhile(1){if(((a*a*a)==((b*b*b)+(c*c*c))))return1; a++;if(a>MAX){ a=1; b++;}if(b>MAX){ b=1; c++;}if(c>MAX){ c=1;}}return0;} int main(){if(fermat())std::cout<<"El último teorema de Fermat ha sido refutado.\n";elsestd::cout<<"El último teorema de Fermat no ha sido refutado.\n";}
Posible salida:
El último teorema de Fermat ha sido refutado.
[editar]Enlaces externos
- Lo que todo programador de C debe saber sobre el comportamiento no definido #1/3.
- Lo que todo programador de C debe saber sobre el comportamiento no definido #2/3.
- Lo que todo programador de C debe saber sobre el comportamiento no definido #3/3.
- El comportamiento no definido puede resultar en viajes en el tiempo (entre otras cosas, pero el viaje en el tiempo es el más divertido).
- Comprender el desbordamiento de enteros en C/C++
- Comportamiento no definido y último teorema de Fermat.
- Diversión con punteros NULL, parte 1 (exploit local en Linux 2.6.30 causado por comportamiento no definido debido a la desreferencia del puntero nulo).
[editar]Véase también
Documentación de C para Comportamiento no definido |