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

Ребят, а почему pascalABC.net не следит за диапазонами числовых переменных? Вот в этом коде программа компилируется и работает, выдает ноль на выходе, хотя тут явное переполнение. Может где-то нужно что-то прописать или еще что-нибудь?

begin var N : byte := 255; { 255 – максимальное значение для байта } N := N + 1; Writeln(N); Readln; end.

потому что нигде по умолчанию не следят. выбирайте тип данных такой, чтобы в него все поместилось. есть и BigInteger, ограниченный только размером памяти…

Не давно об этом говорили. И до этого ещё раньше я эту тему поднимал. Пока добавлять эту фичу не спешат. Хотя по умолчанию оно и не должно проверять, это да.

Вот еще пример, где нет проверки

type
  q=1..9;

begin
  var a,b:q;
  a:=4;
  b:=3*a;
  Writeln(b)
end.

Будет выдано правильное значение 4*3=12, хотя оно и недопустимо по диапазону.

Внешне кажется, что это ошибка компилятора или какой-то run-time библиотеки. Но давайте для начала заглянем в справку для диапазонного типа (type q=1…9 как раз диапазонный)…

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

В PascalABC.NET запись целочисленной константы в виде целого числа означает, что для её хранения будет выбран тип integer, для которого отведено 4 байта. Следовательно, для переменных типа q будет также выделено 4 байта, в которые значение 12 безусловно поместится и переполнения не возникнет.

Можно попытаться возмутиться, что нет контроля выхода за границы диапазона 1…9.

Посмотрим, что пишется на эту тему в классической книге Йенсен К., Вирт Н. “Паскаль. Руководство для пользователя и описания языка” (кто не помнит, Н.Вирт - автор языка Паскаль).

2.2. Целый тип (integer) Значениями целого типа являются являются элементы ЗАВИСЯЩЕГО ОТ РЕАЛИЗАЦИИ (выделено мной) подмножества целых чисел. Там же, (в 5.2. Ограниченные типы), дается обоснование распространения свойств базового типа на диапазонный.

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

Что касается контроля границ для диапазонных типов, Н. Вирт ничего специально об этом не говорил. Однако известно, что он был сторонником идеи строго контролировать все-все. К сожалению, соблюдение этого приводит к огромным накладным расходам в программе, делая её громоздкой, медленной и неэффективной по памяти.

Разработчики PascalABС.NET решили так: контролируется то, что может быть проконтролировано при компиляции. Т.е. попытка записать var x:q:=10; вызовет ошибку при компиляции. А вот присваивание некоторого значения при выполнении программы ошибки не вызовет.

Формально противоречия нет: описание разрядности переменной говорит лишь о том, в каком диапазоне должны быть данные, чтобы разместиться в такой переменной. И не более.

А какая разница между var x:q:=10 и (var x:q; x:=10)? x - диапазонный тип и там, и там, только инициализация в первом случае при объявлении, а во втором отдельной строкой. Зачем тогда эти все диапазонные типы если они не отслеживаются компилятором. Нипанятна.

Мы согласны. Незачем.

1 лайк

Это вы серьезно или шутите? Вот здесь почему нет ошибки? type DayOfWeek = (Mon, Tue, Wed, Thi, Thr, Sat, Sun); var intE: Mon…Thr:=Sat; begin writeln(intE); end.

Ну диапазонным типам вроде нет применения дальше учебных задач, так что за них как то не больно…

Неужели действительно непонятно, или ёрничаете?

type
  q=1..9;

begin
  var x:q:=10;
  var y:q; y:=10
end.

Для начала попробуйте уяснить себе, чем описание отличается от выполняемого оператора. var x:q:=10; - это описание переменной х с одновременной инициализацией. Необходимый код генерируется на стадии компиляции и может быть проконтролирован компилятором. var y:q; y:=10 - это, во-первых, описание переменной y с типом q, которое компилятор контролирует. И во-вторых, это оператор присваивания, который выполняется в готовой программе и компилятором уже не контролируется (к моменту выполнения компилятора уже и след простыл). Поэтому в первом случает компилятор фиксирует ошибку, а во втором - нет. Опять непонятно? Ошибка возникнет при выполнении кода, при компиляции ошибки нет, посему компилятор молчит.

ТщательнЕй надо, ребята! Общим видом овладели, теперь подробности не надо пропускать. (М. Жванецкий)

Серьёзно. Была б наша воля - мы бы вообще убрали диапазонный тип из языка. Потому что поддержка его стоит дорого, а практического смысла в нём мало.

1 лайк

Вообще-то, тут вы несколько не правы. Действительно, при описании с инициализацией компилятору сразу известна полная картина. Но, к слову, на любом этапе компиляции программы ему известны типы всех переменных и типы всех использованных в тексте программы констант. Поэтому ситуация вида var y:q; y:=10 вполне себе разрешима. Вот var y:q; var p:=10; y:=p - уже нет, т.к. требует значительно более глубокого анализа.

Тогда вопрос, почему там (var x:q:=10) может, а тут нет?

type DayOfWeek = (Mon, Tue, Wed, Thi, Thr, Sat, Sun); var intE: Mon…Thr:=Sat;

Я не автор этого компилятора и не могу с полной уверенностью сказать о том, как в данном, конкретном случае проходит контроль. Но в своей жизни мне приходилось заниматься и ими, так что некоторыми общими сведениями я все же располагаю. В интерпретаторах контроль, о котором Вы пишете, при желании реализуется “левой задней ногой”. В компиляторах мы сразу же упираемся в эффективность создаваемой программы. Вы пишете о том, что можно реализовать один дополнительный круг анализа, чтобы выявить возможные “нарушения” в присваивании. Но тогда почему один, а не два или три? Как быть, в таком, вроде бы элементарном случае?

type
   q=1..9;

procefure P(foo:q; var bar:q);
begin
   bar+=foo
end;

begin
  var a:q:=4;
  var b:q:=7;
  P(a,b);
end.

Тут тоже вроде бы “все очевидно”. Но представляете, как надо глубоко “рыть” компилятору?

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

Это не мне вопрос, а авторам компилятора. Тут ситуация несколько сложнее: определяется перечислимый тип на базе integer, а потом на его основе определяется диапазонный. Помня о том, что в PascalABC.NET все типы являются классами (следовательно, все переменные являются объектами соответствующих классов), я не рискну даже предположить, как происходит в этом случае наследование. Но, скорее всего, тут компилятор тоже не может разобраться в ситуации без больших накладных расходов.

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

Говорю не просто так - вон, всей группой полгода писали :slight_smile:

1 лайк

диапазоны не нужны. типичный паскалевский атавизм

3 лайка

Мне тут знающие люди из MS рассказали, что в .NET можно включить контроль переполнения целых. Как-то так:

dotnet run /p:CheckForOverflowUnderflow=true

Почему бы так не сделать в вашем компиляторе?

Вы говорите о создании аналога checked/unchecked из C#?

Да, вижу:

Операция add.ovf

Это надо все операции с целыми генерировать альтернативные.

Ну, немаленькая работа. Интересная - согласен.

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

1 лайк

Видимо, да.

Сценарии более чем очевидны: применение в обучении. Согласитесь, это немалая доля использования вашего прекрасного продукта.

Да и вообще: чего стоят все разговоры о безопасности, контроле типов и пр., если переполнение не контролируется.

1 лайк