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


#22

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

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

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


#23

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

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

И где тут


#24

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

Целое без знака - это машинное слово длиной в регистр процессора. Например, 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 байта, сами догадаетесь, какие биты считаются знаковыми?


#25

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

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


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

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


#27

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

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

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


#28

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

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

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


#29

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

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

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

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


#30

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


#31

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

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


#32

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

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

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


#33

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

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

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

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

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

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

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


#34

Очень рад, что Вы это принимаете. За бурление можно не благодарить, оно возникает помимо моих усилий :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

#35

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


#36

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

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ЕТ - все же “взрослый” язык и он не должен пританцовывать вокруг программиста с погремушкой и соской.


#37

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


#38

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


#39

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

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

#40

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

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

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


#41

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

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