November 7, 2019

Типы с диапазоном

Или Subrange Types типа INT(1..42);

Переменную целого типа можно вписать в заранее ограниченный диапазон. Иначе говоря, ограничить ее значение сверху и снизу. Работает только для переменных целого типа: SINT, USINT, INT, UINT, DINT, UDINT, BYTE, WORD, DWORD, LINT, ULINT, LWORD.

Поиграем в такой песочнице:

PROGRAM MAIN
VAR
    i: DINT;
    value: INT;
    _value_: INT(10..90);
    analog: REAL;

    pValue : PVOID;

Для красоты я обрамил переменную с диапазоном символами подчеркивания. С переменного такого типа можно делать все то же самое, что и с переменными целого типа. По сути это не отдельный тип данных с ограничением, а предписание компилятору по сборке кода и в некоторых случаях (расскажу позже) по контролю за значением переменной.
К сожалению нельзя сделать так: _analog_ : REAL (4.0 .. 20.0); и не потому, что точки мешают, а по причине неточности типов с плавающей точкой.

Примеряем


Что можно делать:

_value_ := 42;
_value_ := value;

_value_ := _value_ + 1;
_value_ := _value_ + 100; // O_o

_value_ := DINT_TO_INT(i);
_value_ := REAL_TO_INT(analog);

Что компилятор не пропустит:

_value_ := 0;
FOR _value_ := 11 TO 89 DO


Играем с указателями


Я решил проверить контролирует ли кто-нибудь во время работы выход значения переменной за указанный диапазон. И через указатель записал ноль в переменную value. И ноль записался (ничего страшного не произошло). А затем я прибавил к переменной единицу и... значение переменной увеличилось на 1:

// _value_ = 10;

value := 0;
pValue := ADR(_value_);
MEMCPY(pValue, ADR(value), SIZEOF(_value_));
// _value_ = 0;

_value_ := _value_ + 1;
// _value_ = 1;

Вообще, если циклически выполнять _value_ := _value_ + 1; то мы увидим весь диапазон значений INT от -32768 до +32767. Без каких-либо ограничений.

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


Неявный контроль времени исполнения


Или `implicit checks`. Это целое семейство ФБ неявного контроля значений переменных (например, ФБ контролирующие деление на ноль):



Чтобы контроль работал во время исполнения программы необходимо добавить в проект функциональные блоки специального назначения:



...выбрать L|Range Checks и пересобрать проект. Добавленные ФБ будут контролировать значение переменной и корректировать его, если значение выйдет за пределы. Если теперь повторно провернуть трюк с указателями, но уже с включенной проверкой, то при следующем обращении к переменной, среда исполнения через соответствующие ФБ исправит/впишет значение переменной в указанный при объявлении диапазон.


Перформанс


Для тех кто не в курсе, измерения производительности выполняются с целью уловить происходит ли дополнительная работа за кулисами или нет. Никакого смысла в оптимизации на 1-2 наносекундах не было и нет, хотя кто знает, что у вас там творится. По вертикали время в сотнях наносекунд: 83000 = 8.3 миллисекунды.

Испытательный полигон:

// HOWMUCH: DINT := 1_000_000;

CASE state OF
1,5:; // пустой цикл

2: // инкремент
    FOR i := 0 TO HOWMUCH DO
        value := value + 1;
    END_FOR

3: // константа, в диапазоне
    FOR i := 0 TO HOWMUCH DO
        _value_ := 42;
    END_FOR

4: // инкремент, выход из диапазона
    FOR i := 0 TO HOWMUCH DO
        _value_ := _value_ + 1;
    END_FOR


Итак, без контроля:


Добавляю контрольные ФБ и пересобираю проект:

Контролируемый выход за пределы диапазона требует дополнительной работы.


Вывод

  • Тип по сути остается обычным целочисленным типом (INT, BYTE, ...).
  • Диапазон — это рекомендация для компилятора.
  • Диапазон учитывается в рантайме только при наличии специальных ФБ, скрыто контролирующих и исправляющих значение переменной.
  • Неявный контроль требует немного дополнительной работы, что логично, так как коррекция значений требует дополнительного вызова ФБ.

No comments

Post a Comment

Note: Only a member of this blog may post a comment.