В нескольких современных языках программирования (включая С++, Java и С#), язык позволяет целочисленное переполнение происходить во время выполнения без какого-либо повышения условия ошибки.
Например, рассмотрим этот (надуманный) метод С#, который не учитывает возможность переполнения/недополнения. (Для краткости этот метод также не обрабатывает случай, когда указанный список является нулевой ссылкой.)
//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;
}
Если этот метод вызывается следующим образом:
List<int> list = new List<int>();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);
Переполнение будет происходить в методе sumList()
(поскольку тип int
в С# представляет собой 32-разрядное целое число со знаком, а сумма значений в списке превышает значение максимального 32-разрядного целого числа со знаком). Переменная суммы будет иметь значение -294967296 (а не значение 4000000000); это скорее всего не то, что предполагалось (гипотетическим) разработчиком метода sumList.
Очевидно, существуют различные методы, которые могут быть использованы разработчиками, чтобы избежать возможности переполнения целочисленного типа, например, использовать такой тип, как Java BigInteger
или checked
и /checked
компилятор в С#.
Однако вопрос, который меня интересует, заключается в том, почему эти языки были разработаны по умолчанию, чтобы обеспечить, прежде всего, переполнение целых чисел, а не, например, повышение исключения, когда операция выполняется во время выполнения, которая приводит к переполнению. Похоже, что такое поведение поможет избежать ошибок в тех случаях, когда разработчик не учитывает возможность переполнения при написании кода, который выполняет арифметическую операцию, которая может привести к переполнению. (Эти языки могли бы включать что-то вроде "непроверенного" ключевого слова, которое могло бы обозначать блок, где допускается целочисленное переполнение без возникновения исключения, в тех случаях, когда это поведение явно предназначено разработчиком; С# на самом деле имеет это.)
Ответ просто сводится к производительности - разработчики языка не хотели, чтобы их соответствующие языки по умолчанию имели "медленные" арифметические операции с целыми числами, в которых время выполнения должно было выполнять дополнительную работу, чтобы проверить, произошло ли переполнение, на каждая применимая арифметическая операция - и это соображение производительности перевешивало ценность исключения "молчащих" сбоев в случае непреднамеренного переполнения?
Существуют ли другие причины для этого решения для дизайна языка, кроме соображений производительности?