39

В нескольких современных языках программирования (включая C++, Java и C#), язык позволяет integer overflow встречаться во время выполнения без повышения какого-либо состояния ошибки.Почему языки по умолчанию не повышают ошибки при переполнении целых чисел?

Например, рассмотрим этот (надуманный) метод C#, который не учитывает возможность переполнения/недогрузки. (Для краткости, метод также не обрабатывает случай, когда указанный список является пустая ссылка.)

//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 в C# представляет собой 32-разрядное целое число со знаком, а сумма значений в списке превышает значение максимального 32-разрядного целого числа со знаком). Переменная суммы будет иметь значение -294967296 (а не значение 4000000000); это скорее всего не то, что предполагал (гипотетический) разработчик метода sumList.

Очевидно, что существует различные методы, которые могут быть использованы разработчиками, чтобы избежать возможностей целочисленного переполнения, например, с использованием типа, как в Java BigInteger или коммутатор checked ключевого слова и /checked компилятора в C#.

Однако вопрос, который меня интересует, заключается в том, почему эти языки были созданы по умолчанию, чтобы обеспечить, прежде всего, переполнение целых чисел, а не, например, повышение исключения, когда операция выполняется во время выполнения, приведет к переполнению. Похоже, что такое поведение поможет избежать ошибок в тех случаях, когда разработчик не учитывает возможность переполнения при написании кода, который выполняет арифметическую операцию, которая может привести к переполнению. (Эти языки могли бы включать что-то вроде «непроверенного» ключевого слова, которое могло бы обозначать блок, где разрешено целочисленное переполнение без возникновения исключения, в тех случаях, когда это поведение явно предназначено разработчиком; C# фактически does have this.)

Отвечает ли ответ просто на производительность - разработчики языка не хотели, чтобы их соответствующие языки по умолчанию имели «медленные» арифметические операции с целыми числами, в которых время выполнения должно было выполнять дополнительную работу, чтобы проверить, произошло ли переполнение, каждая применимая арифметическая операция - и это соображение производительности перевешивало ценность предотвращения «молчащих» сбоев в случае непреднамеренного переполнения?

Существуют ли другие причины для этого решения по языковому дизайну, помимо соображений эффективности?

ответ

31

В C#, это был вопрос о производительности. В частности, готовый бенчмаркинг.

Когда C# был новым, Microsoft надеялась, что многие разработчики C++ переключится на него. Они знали, что многие люди C++ считают C++ быстрым, особенно быстрее, чем языки, которые «тратят время» на автоматическое управление памятью и т. П.

Как потенциальные усыновители, так и рецензенты журнала, скорее всего, получат копию нового C#, установят его, создадут тривиальное приложение, которое никто никогда не будет писать в реальном мире, запустите его в узкой петле и измерьте, как долго это заняло. Затем они приняли решение для своей компании или опубликовали статью на основе этого результата.

Тот факт, что их тест показал, что C# будет медленнее, чем изначально скомпилированный C++, является тем, что быстро отключит людей от C#.Тот факт, что ваше приложение C# будет автоматически перехватывать/переполнять, - это то, что они могут пропустить. Таким образом, он отключен по умолчанию.

Я думаю, что очевидно, что 99% времени мы хотим/проверили, чтобы быть включенным. Это неудачный компромисс.

24

Я думаю, что производительность - довольно веская причина. Если вы рассматриваете каждую инструкцию в типичной программе, которая увеличивает целое число, и если вместо простого оп добавить 1, он должен был проверять каждый раз, если добавление 1 переполнило бы тип, тогда стоимость дополнительных циклов была бы довольно серьезной.

+2

Не возвращается ли ЦП флаг при переполнении операции? (Конечно, проверка этого флага также требует времени). – Thilo 2017-03-24 04:28:51

+0

@Thilo есть процессоры без каких-либо флагов, таких как MIPS. Даже делать простые операции с большим int на них - небольшая боль – 2017-08-14 07:59:33

7

Скорее всего, 99% производительность. На x86 нужно было бы проверить флаг переполнения при каждой операции, которая была бы огромной.

Другие 1% будут охватывать те случаи, когда люди делают причудливые манипуляции с битами или «неточны» при смешивании подписанных и неподписанных операций и требуют семантики переполнения.

7

Поскольку проверка переполнения требует времени.Каждая примитивная математическая операция, которая, как правило, преобразуется в одну инструкцию по сборке, должна включать проверку переполнения, приводящую к нескольким инструкциям по сборке, что может привести к тому, что программа будет в несколько раз медленнее.

-4

Мое понимание того, почему ошибки не будут повышаться по умолчанию во время выполнения, зависит от наследия, желающего создать языки программирования с ACID-подобным поведением. В частности, принцип, что все, что вы его кодируете (или не кодируете), будет делать (или не делать). Если вы не закодировали какой-либо обработчик ошибок, тогда машина будет «предполагать» в силу отсутствия обработчика ошибок, что вы действительно хотите сделать нелепую, склонную к сбою вещь, о которой вы говорите.

(ACID ссылка: http://en.wikipedia.org/wiki/ACID)

+5

Я не вижу никакой связи между свойствами ACID и тем, что вы описываете. Объясните, пожалуйста, подключение ... – 2009-09-21 12:28:21

5

Обратная совместимость является большой. С C предполагалось, что вы уделяете достаточное внимание размерам ваших типов данных, которые, если произошло превышение/недополнение, это то, что вы хотели. Затем с C++, C# и Java очень мало изменилось с тем, как работают «встроенные» типы данных.

14

Вы работаете в предположении, что целочисленное переполнение всегда является нежелательным поведением.

Иногда цельное переполнение является желательным поведением. Одним из примеров, который я видел, является представление абсолютного значения курса как числа с фиксированной точкой. Учитывая unsigned int, 0 равен 0 или 360 градусов, а максимальное 32-разрядное целое без знака (0xffffffff) является самым большим значением чуть ниже 360 градусов.

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; 

} 

Возможно, существуют другие ситуации, когда переполнение допустимо, аналогично этому примеру.

+7

Есть, вероятно, много других случаев, когда переполнение целых чисел приводит к неправильным результатам, а затем возвращает правильный результат. Фактически, единственный раз, когда он на самом деле генерирует правильные результаты, это когда это ожидаемое поведение, и оно фактически используется как функция, например, в вашем примере. – Kibbee 2008-09-19 18:33:06

7

C/C++ никогда не управляет ловушкой. Даже очевидное деление на 0 - неопределенное поведение в C++, а не определенный тип ловушки.

На языке C нет концепции захвата, если вы не подсчитываете сигналы.

У C++ есть принцип дизайна, который не вводит накладные расходы, не присутствующие на C, если вы не попросите об этом. Таким образом, Страуструп не хотел бы указывать на то, что целые числа ведут себя таким образом, который требует какой-либо явной проверки.

Некоторые ранние компиляторы и легкие реализации для ограниченного оборудования не содержат исключений вообще, и исключения часто могут быть отключены с помощью параметров компилятора. Мандатирование исключений для встроенных языков было бы проблематичным.

Даже если C++ был сделан целые числа проверены, 99% программисты в первые дни превратились бы, если выключить для повышения производительности ...