Ошибка переполнения переменной.

Переполнение должно контролироваться аппаратно, т.е. вызывать аппаратное прерывание, которое может быть затем обработано операционной системой. В противном случае такой контроль драматически снижает производительность. Это было известно еще со времен ЭВМ второго поколения. Самая массовая серия ЭВМ третьего поколения IBM\360 - 370 и их советские клоны EC ЭВМ - располагала процессором, в котором была аппаратно реализована целочисленная арифметика, арифметика с плавающей точной и десятичная арифметика. Так вот: этот процессор не имел аппаратных прерываний для целочисленной арифметики. То же самое с процессорами современных “персоналок”, будь они от Intel, AMD или Apple. То же практически со всеми специализированными процессорами.

Причина отсутствия аппаратного прерывания процессора для целочисленной арифметики кроется в том, что оно бесполезно. Существует лишь одна общая инструкция загрузки некоторого количества байт в регистры процессора и он “не понимает”, это число со знаком или беззнаковое - это лишь интерпретация программиста. А поскольку переполнение связано с неверной установкой именно знакового разряда, делать аппаратное прерывание означает на деле получить массу ложных его возникновений.

Посему Ваше эмоциональное высказывание - это так, выхлоп в Космос.

А вот это я не понял. Разве проверка переполнения реализуется не как то так (но, разумеется, на аппаратном уровне):

function Add_Ovf(a,b: integer): integer;
begin
  Result := a+b;
  if Result < a then
    raise new System.OverflowException;
end;

И где тут

Вы действительно не поняли. На аппаратном уровне проверка переполнения в целочисленной арифметике вообще не реализована, о чем я столько времени старательно пытался написать. И вот почему.

Целое без знака - это машинное слово длиной в регистр процессора. Например, 32 бита.

Пусть в регистре находится значение 0xFFFFFFFF = 11111111 11111111 11111111 11111111

Если его интерпретировать, как Unsigned integer (32-bit), это будет десятичное число 4294967295, а если как Signed integer (32-bit) - это будет десятичое число -1.

Загрузим в регистр значение 0x7FFFFFFF = 01111111 11111111 11111111 11111111

Процессор не имеет понятия, что это было - целое со знаком или без, число с плавающей точкой или просто набор байт, представляющих символы.

Загрузим в другой регистр значение 0x00000001 = 00000000 00000000 00000000 00000001

А теперь попросим процессор сложить содержимое этих регистров, как целые числа.

   0x7FFFFFFF = 01111111 11111111 11111111 11111111
+  0x00000001 = 00000000 00000000 00000000 00000001
=  0x80000000 = 10000000 00000000 00000000 00000000

А теперь вопрос - это было переполнение при сложении двух целых чисел со знаком (и по нему надо делать прерывание) или нормальное сложение двух беззнаковых 32-битных целых (и по нему прерывание делать не надо) ?

Этого процессор не знает, потому что у него нет двух разных операций сложения.

Напоследок - еще один “убойный” момент. Современные процессоры персоналок - 64-битные. И регистры у них такие же. Когда мы складываем числа длиной 1, 2 или 4 байта, сами догадаетесь, какие биты считаются знаковыми?

3 лайка

Если уже добавлять инструкции для сложения с контролем переполнения - какая разница сколько их добавить? Тех же операторов сравнения (> и <) для каждого размера и знаковости сделали же.

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

Дерзайте ныне ободренны
Раченьем вашим показать,
Что может собственных Платонов
И быстрых разумом Невтонов
Российская земля рождать.
          М.В. Ломоносов

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

Программно эмулируемые это вообще другая история. Если эмулировать программно, проблемы которые вы описали выше - не проблемы.

Разумеется, я говорил про добавление инструкции на этапе создания новой модели процессора.

И - вы пока только 1 раз вскользь сказали что не видели процессоры в которых эта инструкция реализована (а как много вы проверили?) и теперь говорите в стиле “нету - наверное значит есть причина”, что не даёт никакой полезной информации и может использоваться только для убийства дискуссии.

Т.е. ради сомнительного “новшества” Вы предлагаете разработать процессор, который будет иметь расширенную систему команд, отличную от ныне распространенных процессоров, что потребует проектных разработок, аппаратная реализация несомненно удорожит такой процессор (вспомните и сравните стоимость процессоров CISC и RISC). А потом еще и разработчики ПО должны будут озадачиться выпуском отдельного софта, позволяющего работать с этими новыми командами, т.е. поддерживать дополнительные семейства процессоров. И снова повторю - ради чего? Ублажить небольшую группку людей, которые не в состоянии определить возможность возникновения переполнения в операциях с индексами и счетчиками? Поскольку для иных целей целочисленная арифметика используется крайне редко.

Ну если для Вас IBM\360-370-380, Intel, AMD и Apple не показательны, уж и не знаю… Найдите в литературе, что ли, ссылки на процессоры, которые имеют возможность выдавать аппаратные прерывания по переполнению в операциях с целочисленными операндами разного типа.

И опять Вы неправы: причину я указал. Пожалуйста, научитесь наконец внимательно читать написанное, если хотите возражать по существу.

Я ничего не предлагаю. checked это защита от дурака, и не особо полезная.

Но я не согласен только с тем что вы назвали изначальной проблемой. Да и производительность тоже, всё же в наше время повседневных задачах производительность это не так важно как безопастность. И наличие таких инструкций не замедлило бы скорость выполнения задач где производительность важна. Поэтому это не настолько уж и нереально.

Я не посчитал это за 100% достоверную информацию, потому что Intel, AMD и Apple делают множество разных процессоров. Даже если у большинства их процессоров нету таких инструкций - не значит что у всех нету. И - вы ведь тоже не проверили все модели процессоров? Если что - я, опять же, не предлагаю это делать)).

У меня нет привычки прыгать через части сообщений. Я говорил про конкретную часть вашего сообщения, про то что она не о чём.

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

В современных ЯП слишком много сахара. От этого многим становится приторно.

Иногда и самому нужно думать.

Ввести в язык можно всё что угодно, но стоит ли оно того? (вопрос риторический)

Касательно ввода checked/unchecked в язык. Перед добавлением нового средства в язык всегда стоит отвечать на вопрос (это я говорю @c3c): Насколько это будет востребовано? Стоит помнить, что при ответе следует учитывать целевую аудиторию PascalABC.NET. Нет, я не отговариваю и не призываю разработчиков вводить данную конструкцию в язык, я лишь обращаю внимание @c3c на данные вопросы.

Лично моё мнение таково: если бы PascalABC.NET был признан официально языком для промышленной разработки, то в таком случае вероятность ввода вышеуказанной конструкции была бы больше. Её отсутствие в языке мне не мешает.

1 лайк

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

Производительность потестирую чуть позже - неизвестно, в какие машинные коды это затем переводится. Но даже при проседании производительности это можно было бы использовать например в Debug-режиме для контроля за некоторыми ошибками. А потом в Release-режиме можно было бы запускать программы более безопасно.

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

a := a + b;
b := a - b;
a := a - b;

я бы мог предложить запустить этот код в режиме check overflow

Так что идея бесспорно хорошая.

Отдельное спасибо, что Вы породили тут на форуме такое бурное обсуждение :wink:

2 лайка

Очень рад, что Вы это принимаете. За бурление можно не благодарить, оно возникает помимо моих усилий :wink:

Ну, и чтоб два раза не вставать:

> program TenPow13;
> var
>    x, i: integer;
> begin
>    println(maxint);
>    x := 10;
>    i := x*sqr(sqr(x*sqr(x)));
>    println(i, x*sqr(sqr(x*sqr(x))));
> end.   

И вывод:

> 2147483647 
> 1316134912 10000000000000

Если это будет только в отладочном режиме, да и то опционально - ради бога, пусть будет. ))) В конце-концов обсуждение - оно не для того, чтобы пытаться кого-либопринудить, а лишь показать разные аспекты явления.

1 лайк

А что, религия не позволяет написать что-то вроде

begin
  integer.MaxValue.Println;
  var x := BigInteger(10);
  var i := x * sqr(sqr(x * sqr(x)));
  println(i, x * sqr(sqr(x * sqr(x))));
end. 

Сошлюсь на свою же книгу (см. 1.7)

PascalABC.NЕТ - все же “взрослый” язык и он не должен пританцовывать вокруг программиста с погремушкой и соской.

3 лайка

Программист может писать так не задумываясь - и не получит ошибку. Речь о том чтобы скажем в Debug-режиме ему кто-то об этом сигнализировал

И хорошо бы (еще раз об этом), чтобы опционально, потому что в Debug-режиме удобнее работать при написании и отладке, т.е. он по умолчанию все же у большинства программистов включен. Посему если хочется писать “не задумывясь” - пусть эту опцию и включают в отладке, но по умолчанию все же, как мне представляется, она должна быть выключена. Программист, который пишет не задумываясь - это не программист, а кодер.

4 лайка

И, кстати, придется заниматься обработкой всех вот таких случаев:

  • совместное оперирование знаковыми и беззнаковыми целыми (например, сравнение integer и uint32);
  • усечение целых (например, усечение integer до smallint);
  • потеря значимости и переполнение (например, в результате суммирования двух целых может быть получено число большее, чем максимально возможное для целого типа данных).

Я согласен, что типизация (трактовка) данных ложится на плечи программиста, потому что это больше человеческий фактор и, значит, нужно контролировать хотя бы на стадии отладки.

С другой стороны сам Вирт в 90-х весьма активно интересовался темой ППВМ/ПЛИС в перспективе перепрограммируемого пользователем оборудования, что стало причиной создания станции Хамелеон (Ceres-3) и языка описания аппаратуры Lola.

К сожалению, крупные коммерческие проекты и скорый выход на пенсию несколько сменили интересы учёного.

Да, это могло бы быть интересным. Но что имеем - то имеем. А имеем ситуацию, когда процессор сам по себе не в состоянии понять, что происходит при сравнении знакового и беззнакового целого, поскольку он их не различает. Люди порой сами себе придумывают различные интерпретации содержимого машинной памяти, а потом возмущаются (или хотя бы просто сетуют), что процессор не отлавливает ошибок при оперировании такими данными.

Не буду придумывать каких-то экзотическим примеров, возьму готовый из арифметики ЭВМ третьего поколения, таких как IBM\370, которые теперь зовут “мэйнфреймами”. Там была аппаратно реализована двоично-десятичная арифметика. Каждая цифра числа кодировалась своим двоичным представлением, занимая 4 бита, а две цифры паковались в байт. Поэтому число 35427 выглядело как 0х035427 и занимало 3 байта. Старший (левый) бит был знаковым, поэтому 2415 записывалось как 0х2415, а -2415 имело вид 0хА415. Вроде все просто и понятно. Но эта арифметика предполагала, что такие числа могут содержать фиксированную точку, причем позиция точки в представлении числа не хранилась. Компилятор (или программист на ассемблере) должен был эту точку подразумевать. Существовала сложная система правил, определяющих местоположение точки в результате из-а необходимости выравнивания частей числа по позиции десятичной точки. В частности, для операции деления было правило, что его надо производить, сохраняя дробную часть с максимально возможной точностью. А длина такого числа всегда занимала известное фиксированное количество байт. Поэтому был так знаменит пример 15 + 1 / 3 = 5.333333333 … 3. В целой части числа старший разряд молча усекался. Драматично, не правда ли? Никаких сообщений, никаких прерываний.

1 лайк