c++ - tipos - uint8_t que es
¿Qué sucede exactamente cuando un entero de 32 bits se desborda en una máquina de 64 bits? (3)
El problema con su razonamiento es que comienza con la suposición de que el desbordamiento de enteros resultará en una operación determinista y predecible.
Desafortunadamente, este no es el caso: un comportamiento indefinido significa que cualquier cosa puede suceder, y especialmente que los compiladores pueden optimizar como si nunca hubiera ocurrido .
Como resultado, es casi imposible predecir qué tipo de programa producirá el compilador si existe un posible desbordamiento.
- Una salida posible es que el compilador elude la asignación porque no puede suceder
- Una posible salida es que el valor resultante es 0 extendido o signo extendido (dependiendo de si se sabe que es positivo o no) y se interpreta como un entero sin signo. Puede obtener cualquier cosa desde
0
hastasize_t(-1)
y, por lo tanto, puede asignar muy poca o mucha memoria, o incluso no asignar, ... - ...
Comportamiento indefinido => Todas las apuestas están desactivadas
La situación es la siguiente:
- un entero de 32 bits se desborda
- malloc, que espera un entero de 64 bits, utiliza este entero como entrada
Ahora en una máquina de 64 bits, la declaración es correcta (si la hay) :
Digamos que el entero binario firmado 11111111001101100000101011001000 es simplemente negativo debido a un desbordamiento. Este es un problema práctico ya que es posible que desee asignar más bytes de los que puede describir en un entero de 32 bits. Pero luego se lee como un entero de 64 bits.
-
Malloc
lee esto como un entero de 64 bits, encontrando11111111001101100000101011001000################################
con # siendo un bit comodín representando lo que sea Los datos se almacenan después del entero original. En otras palabras, lee un resultado cercano a su valor máximo 2 ^ 64 e intenta asignar algunos quintillones de bytes. Fracasa. -
Malloc
lee esto como un entero de 64 bits, y se0000000000000000000000000000000011111111001101100000101011001000
a0000000000000000000000000000000011111111001101100000101011001000
, posiblemente porque es así como se carga en un registro dejando muchos bits a cero. No falla, pero asigna la memoria negativa como si leyera un valor sin signo positivo. -
Malloc
lee esto como un entero de 64 bits,Malloc
en################################11111111001101100000101011001000
, posiblemente porque es como se carga en un registro con # un comodín que representa los datos que se encontraban previamente en el registro. Falla bastante impredeciblemente dependiendo del último valor. - El entero no se desborda en absoluto porque, aunque es de 32 bits, todavía está en un registro de 64 bits y, por lo tanto, malloc funciona bien.
De hecho, probé esto, lo que provocó que el malloc fallara (lo que implicaría que 1 o 3 sean correctos). Supongo que 1 es la respuesta más lógica. También sé la solución (usando size_t como entrada en lugar de int).
Solo quiero saber lo que realmente sucede. Por alguna razón, no encuentro ninguna aclaración sobre cómo los enteros de 32 bits se tratan realmente en máquinas de 64 bits para un "reparto" tan inesperado. Ni siquiera estoy seguro de si está en un registro realmente importa.
Entonces, si tenemos un ejemplo de código específico, un compilador y una plataforma específicos, probablemente podamos determinar qué está haciendo el compilador. ¿Cuál es el enfoque adoptado en Deep C, pero incluso entonces puede que no sea completamente predecible, lo que es un sello de comportamiento indefinido? No es una buena idea generalizar sobre un comportamiento indefinido.
Solo tenemos que echar un vistazo a los consejos de la documentación de gcc
para ver qué tan complicado puede ser. La documentación ofrece algunos buenos consejos sobre el desbordamiento de enteros , que dice:
En la práctica, muchos programas portátiles de C suponen que el desbordamiento de enteros con signo se ajusta de manera confiable utilizando la aritmética de complemento de dos. Sin embargo, el estándar de C dice que el comportamiento del programa no está definido en el desbordamiento, y en algunos casos los programas de C no funcionan en algunas implementaciones modernas porque sus desbordamientos no se ajustan como lo esperaban sus autores.
y en la subsección Consejos prácticos para problemas de desbordamiento firmados dice:
Idealmente, el enfoque más seguro es evitar el desbordamiento de enteros con signo por completo. [...]
Al final del día, es un comportamiento indefinido y, por lo tanto, impredecible en el caso general, pero en el caso de gcc
, en su sección de Implementación definida en Integer dice que el desbordamiento de enteros se ajusta:
Para la conversión a un tipo de ancho N, el valor se reduce en módulo 2 ^ N para estar dentro del rango del tipo; no se levanta ninguna señal.
pero en sus consejos sobre el desbordamiento de enteros, explican cómo la optimización puede causar problemas con el wraparound :
Los compiladores a veces generan código que es incompatible con la aritmética de enteros envolventes.
Así que esto se complica rápidamente.
Una vez que un entero se desborda, usar su valor resulta en un comportamiento indefinido. Un programa que usa el resultado de un int
después del desbordamiento no es válido de acuerdo con el estándar; esencialmente, todas las apuestas sobre su comportamiento están desactivadas.
Con esto en mente, veamos lo que sucederá en una computadora donde los números negativos se almacenan en la representación del complemento a dos. Cuando agrega dos enteros grandes de 32 bits en dicha computadora, obtiene un resultado negativo en caso de un desbordamiento.
Sin embargo, según el estándar de C ++, el tipo de argumento de malloc
, es decir , size_t
, siempre está sin firmar . Cuando convierte un número negativo en un número sin signo, se extiende con el signo ( ver esta respuesta para una discusión y una referencia al estándar ), lo que significa que el bit más significativo del original (que es 1
para todos los números negativos) es establecido en los 32 bits superiores del resultado sin firmar.
Por lo tanto, lo que obtienes es una versión modificada de tu tercer caso, excepto que en lugar de "comodín #
bits" tiene unos hasta el final. El resultado es un número gigantesco sin firmar (aproximadamente 16 exbibytes aproximadamente); naturalmente malloc
no puede asignar tanta memoria.