Se llamaba 2147483647

Érase una vez un número, pero no un número cualquiera, era un número perverso y malvado. Era tan terrible que ninguna variable entera con signo quería alcanzar el valor de este número, porque destruía todos los sistemas en que aparecía. Se llamaba 2147483647.

Vale, realmente no es que los destruyera, sino que… digamos… daba lugar a resultados inesperados. Imaginad un programa que use variables del tipo entero con signo, es decir, la mayoría (es el tipo int de toda la vida). Imaginad que el programa realiza operaciones con estos datos, ¿cómo puede un programador asumir un resultado no controlado por él, lo cual sucedería si una sola variable alcanza este número maldito? OK, me refería a un buen programador, algunos no tienen problema en que su programa haga cosas inesperadas.

Eso de alcanzar y superar este número es lo que se llama técnicamente desbordamiento de entero, y no, no es el demonio ni un error que se encuentre por defecto en todas las computadoras. No es una especie de 666 que asusta a los bits y derrite los chips, ni nada que se le parezca. Es un error que hay que conocer y estudiar para que nuestro programa no arroje esos resultados inesperados.

Desbordamiento

El problema inicial se encuentra en las arquitecturas de 32 bits, donde el signo positivo o negativo del entero va en el bit más significativo, quedando el resto para el dato en sí. Por lo tanto, el valor máximo que pueden tener es 01111111111111111111111111111111, en decimal 2147483647. Si tenemos un contador que alcance este valor, el siguiente es el número anterior invertido, en decimal -2147483647 (error), y después se sigue sumando 1 hasta llegar de nuevo a cero. Si en las operaciones del programa introducimos un valor erróneo obtenemos, como decíamos, resultados inesperados. Al final esto es lo que se llama una excepción software, ocurre algo para lo que no existe control de programa. ¿Y cómo se controla? La respuesta es sencilla, su ejecución no tanto: evitando que se produzcan eventos desconocidos, tratando de prevenir todos los eventos que pueden tener lugar en tiempo de ejecución.

(jajaja, risas mil, ¿para eso me estoy leyendo tu post, amigo?)

Intentamos concretar un poco. Para entender mejor el problema, he aquí un perfecto ejemplo en el que un usuario no privilegiado manipula la entrada a un programa para lograr el acceso a una zona privada:

http://www.ellaberintodefalken.com/2014/04/desbordamiento-de-enteros-integer-overflow.html

En este caso, el problema está en el control de los parámetros de entrada. Se debería haber controlado que el usuario puede introducir en una variable números del orden de 2147483647. Y cómo controlarlo. En el ejemplo, se suman tres variables que desbordan el resultado, por tanto ninguna debería poder tomar valores superiores a un tercio del máximo, con un SI var>=(parte entera de 2147483647/3) ENTONCES error_de_programa; pero también almacenando el dato de entrada del usuario en un array y convertirlo a entero posteriormente, hay liberías que permiten detectar si hay desbordamiento en la conversión:

http://tecnologiasweb.blogspot.com.es/2010/11/vulnerabilidad-de-desbordamiento-de.html

En este ejemplo concreto, habría sido suficiente usar el tipo uint (unsigned integer), ya que se pretende precisamente que los parámetros sean positivos; pero qué pasa si queremos manejar números mayores, pues que tenemos los tipos long y ulong, de 64 bits en vez de 32, lo que nos permite llegar a 9 223 372 036 854 775 807 y 18 446 744 073 709 551 615 respectivamente, claro que siempre habrá un límite. Todo esto entra dentro de las tareas de desinfección de parámetros, que abarca controlar todos los valores que podrían tomar, en este caso aplicado al desbordamiento de enteros.

¿Y qué hacemos cuando pensamos que se pueden alcanzar estos límites? Pues controlarlo.

Otro ejemplo recurrente es la fecha de los ordenadores: ¿Sabías que la mayoría de los sistemas operativos sólo pueden contar hasta el 19 de enero de 2038? No creo que sea relevante porque en 2038 habrá cambiado tanto la tecnología que no tendrá sentido hablar de ello, pero lo analizamos. El error que arrastran en este caso es calcular la fecha en base a una de estas variables tipo int, y lo hacen almacenando el número de segundos que pasan desde el día 1 de enero de 1970, a las 00:00 horas. Esto permite dimensionar la fecha desde 68 años antes de la referencia (si se consideran enteros con signo) hasta 68 años después. Usar un tipo long para almacenar la fecha elimina el problema a efectos prácticos.

Para hablar de esto tendríamos que decir que los sistemas operativos se fian normalmente del llamado reloj del sistema, el cual es hardware y está en un chip CMOS que está permanentemente alimentado, pero necesita muy muy poca energía. Por ello se usa para implementar las configuraciones básicas del sistema, como son la fecha y la hora y las configuraciones de arranque que tenemos en las BIOS, ahora UEFI. Si se quiere cambiar el formato de almacenamiento de la fecha, aquí es donde hay que tocar. Pero también hay otros modelos de actualización de la hora por red, como NTP, lo que da autonomía al sistema operativo para gestionar el método de almacenamiento de la fecha y hora del sistema, que se actualizaría contra unos u otros servidores de NTP. En tal caso podría usarse una variable para almacenar el año y otra para almacenar los segundos que pasan desde las 00:00 horas del 1 de enero de ese año, por ejemplo. Lo que permite contar 2.147 millones de años, suficiente.

Básicamente, hay soluciones más que de sobra y el efecto 2038 no es una catástrofe. Pero no es menos importante conocer muy bien todas las operaciones que realiza un programa, para así valorar un hipotético integer overflow que puede hacer que se vayan al traste muchas cosas. Un error similar se achaca al fallo del cohete Ariane 5, que se destruyó en su lanzamiento:

http://www.rtve.es/noticias/20140604/error-software-convirtio-lanzamiento-espacial-carisimos-fuegos-artificiales/948262.shtml

Fuentes:

https://www.mql5.com/es/docs/basis/types/integer/integertypes

El problema del año 2038 (Efecto Y2K38)

http://blogthinkbig.com/numero-2-147-483-647-especialmente-importante/

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *