language agnostic - ¿Por qué los idiomas no generan errores en el desbordamiento de enteros de forma predeterminada?
language-agnostic integer (8)
C / C ++ nunca exige comportamiento de trampa. Incluso la división obvia por 0 es un comportamiento indefinido en C ++, no un tipo específico de trampa.
El lenguaje C no tiene ningún concepto de captura, a menos que cuentes las señales.
C ++ tiene un principio de diseño que no introduce sobrecarga no presente en C a menos que usted lo solicite. Así que Stroustrup no hubiera querido ordenar que los enteros se comporten de una manera que requiera una verificación explícita.
Algunos compiladores antiguos e implementaciones livianas para hardware restringido no admiten excepciones, y las excepciones a menudo se pueden deshabilitar con las opciones del compilador. Exigir excepciones para el lenguaje incorporado sería problemático.
Incluso si C ++ hubiera hecho números enteros, el 99% de los programadores en los primeros días se habrían desactivado para aumentar el rendimiento ...
En varios lenguajes de programación modernos (incluidos C ++, Java y C #), el lenguaje permite que el desbordamiento de enteros ocurra en el tiempo de ejecución sin generar ningún tipo de condición de error.
Por ejemplo, considere este método (artificial) de C #, que no tiene en cuenta la posibilidad de desbordamiento / subdesbordamiento. (Para abreviar, el método tampoco maneja el caso donde la lista especificada es una referencia nula).
//Returns the sum of the values in the specified list.
private static int sumList(List<int> list)
{
int sum = 0;
foreach (int listItem in list)
{
sum += listItem;
}
return sum;
}
Si este método se llama de la siguiente manera:
List<int> list = new List<int>();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);
Se producirá un desbordamiento en el método sumList()
(porque el tipo int
en C # es un entero de 32 bits con signo, y la suma de los valores en la lista excede el valor del entero máximo de 32 bits con signo). La variable de suma tendrá un valor de -294967296 (no un valor de 4000000000); esto probablemente no es lo que pretendía el desarrollador (hipotético) del método sumList.
Obviamente, hay varias técnicas que pueden usar los desarrolladores para evitar la posibilidad de un desbordamiento de enteros, como el uso de un tipo como BigInteger
de Java o la palabra clave checked
y el interruptor del compilador /checked
en C #.
Sin embargo, la pregunta que me interesa es por qué estos lenguajes fueron diseñados para permitir por defecto que se produzcan desbordamientos de enteros, en lugar de, por ejemplo, generar una excepción cuando se realiza una operación en tiempo de ejecución que daría como resultado un rebosar. Parece que dicho comportamiento ayudaría a evitar errores en los casos en que un desarrollador no tenga en cuenta la posibilidad de desbordamiento al escribir código que realiza una operación aritmética que podría provocar un desbordamiento. (Estos lenguajes podrían haber incluido algo así como una palabra clave "no seleccionada" que podría designar un bloque donde se permite el desbordamiento de enteros sin que se genere una excepción, en los casos en que el desarrollador explícitamente pretenda ese comportamiento; C # realmente tiene esto) . )
¿La respuesta simplemente se reduce al rendimiento? Los diseñadores de idiomas no querían que sus respectivos idiomas tengan operaciones aritméticas enteras "lentas" por defecto donde el tiempo de ejecución necesitaría hacer un trabajo extra para verificar si ocurrió un desbordamiento, en cada aritmética aplicable operación - y esta consideración de rendimiento excedió el valor de evitar fallas "silenciosas" en el caso de que ocurra un desbordamiento inadvertido?
¿Hay otras razones para esta decisión de diseño del lenguaje también, aparte de las consideraciones de rendimiento?
Creo que el rendimiento es una buena razón. Si considera cada instrucción en un programa típico que incrementa un número entero, y si en lugar de la simple opción de agregar 1, tuviera que verificar cada vez que agregar 1 desbordara el tipo, entonces el costo en ciclos extra sería bastante severo.
En C #, era una cuestión de rendimiento. Específicamente, evaluación comparativa fuera de la caja.
Cuando C # era nuevo, Microsoft esperaba que muchos desarrolladores de C ++ lo cambiaran. Sabían que muchas personas de C ++ pensaban que C ++ era rápido, especialmente más rápido que los lenguajes que "malgastaban" el tiempo en la administración automática de memoria y cosas por el estilo.
Es probable que tanto los posibles adoptantes como los revisores de revistas obtengan una copia del nuevo C #, lo instalen, construyan una aplicación trivial que nadie escriba en el mundo real, la ejecute en un círculo cerrado y mida cuánto tiempo tomó. Luego tomarían una decisión sobre su empresa o publicarían un artículo basado en ese resultado.
El hecho de que su prueba mostrara que C # fuera más lento que C ++ compilado de forma nativa es el tipo de cosa que haría que la gente se desconecte rápidamente de C #. El hecho de que su aplicación C # atrape el desbordamiento / desbordamiento automáticamente es el tipo de problema que podrían perderse. Por lo tanto, está apagado por defecto.
Creo que es obvio que el 99% del tiempo queremos / comprobamos que esté activado. Es un compromiso desafortunado.
Es probable un 99% de rendimiento. En x86 tendría que verificar el indicador de desbordamiento en cada operación, lo que supondría un enorme golpe de rendimiento.
El otro 1% cubriría aquellos casos en los que las personas realizan manipulaciones de bits sofisticadas o son "imprecisas" al mezclar operaciones firmadas y sin firmar y desean la semántica de desbordamiento.
La compatibilidad con versiones anteriores es grande. Con C, se suponía que estaba prestando suficiente atención al tamaño de sus tipos de datos que, si ocurría un sobre / subdesbordamiento, eso era lo que quería. Luego, con C ++, C # y Java, muy poco cambió con el funcionamiento de los tipos de datos "integrados".
Mi comprensión de por qué los errores no se generarán de forma predeterminada en el tiempo de ejecución se reduce al legado de desear crear lenguajes de programación con un comportamiento similar al ACID. Específicamente, el principio de que cualquier cosa que codifique que haga (o no codifique) lo hará (o no). Si no codificaste algún manejador de errores, entonces la máquina "asumirá" en virtud de que no hay ningún manejador de errores, que realmente quieres hacer lo ridículo y propenso a los bloqueos que le estás diciendo que haga.
(Referencia ACID: http://en.wikipedia.org/wiki/ACID )
Porque controlar el desbordamiento lleva tiempo. Cada operación matemática primitiva, que normalmente se traduce en una única instrucción de ensamblaje, debería incluir una verificación de desbordamiento, lo que da como resultado múltiples instrucciones de ensamblaje, lo que podría resultar en un programa que es varias veces más lento.
Usted trabaja bajo la suposición de que el desbordamiento de enteros es siempre un comportamiento no deseado.
A veces, el desbordamiento de enteros es el comportamiento deseado. Un ejemplo que he visto es la representación de un valor de rumbo absoluto como un número de punto fijo. Dado un int sin signo, 0 es 0 o 360 grados y el entero máximo sin signo de 32 bits (0xffffffff) es el mayor valor justo debajo de 360 grados.
int main()
{
uint32_t shipsHeadingInDegrees= 0;
// Rotate by a bunch of degrees
shipsHeadingInDegrees += 0x80000000; // 180 degrees
shipsHeadingInDegrees += 0x80000000; // another 180 degrees, overflows
shipsHeadingInDegrees += 0x80000000; // another 180 degrees
// Ships heading now will be 180 degrees
cout << "Ships Heading Is" << (double(shipsHeadingInDegrees) / double(0xffffffff)) * 360.0 << std::endl;
}
Probablemente haya otras situaciones en las que el desbordamiento sea aceptable, similar a este ejemplo.