Инструменты пользователя

Инструменты сайта


oberon:intpack

Упаковка значений вместо битовых полей

Написано ИИ:

В языках семейства C битовые поля появились как попытка описывать аппаратные регистры и сетевые пакеты «в лоб», через структуру.

struct
{
  uint32_t dlc : 4;
  uint32_t ide : 1;
  uint32_t     : 3;
  uint32_t fi  : 8;
};

Но за внешней простотой скрывается несколько неприятных особенностей:

  • порядок битов зависит от реализации;
  • выравнивание зависит от компилятора и ABI;
  • поведение при чтении и записи часто платформозависимо;
  • код плохо переносится;
  • компилятор может генерировать неожиданно тяжёлые операции.

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

Это оказывается не только проще и переносимее, но и значительно более общим методом.

Идея: число как контейнер

Допустим, есть 32-битное значение:

31                    0
+--------+---+--------+
|   fi   |ide|  dlc   |
+--------+---+--------+

Пусть:

  • dlc занимает 4 бита;
  • ide занимает 1 бит;
  • 3 бита пропущены;
  • fi занимает 8 бит.

Тогда всё это можно хранить в одном INTEGER.

Вместо специальных битовых полей используются обычные операции:

  • MOD
  • DIV
  • +
  • -

Формирование полного значения

Пусть:

dlc = 9
ide = 1
fi  = 0ABH

Размещение:

Поле Смещение
dlc 0
ide 4
fi 8

Тогда:

MODULE Example;
 
IMPORT Out;
 
PROCEDURE Go*;
VAR
  dlc: INTEGER;
  ide: INTEGER;
  fi: INTEGER;
  v: INTEGER;
BEGIN
  dlc := 9;
  ide := 1;
  fi := 0ABH;
 
  v := dlc
    + ide * 10H
    + fi  * 100H;
 
  Out.Int(v, 8);
  Out.Ln
END Go;
 
END Example.

Получится:

0000AB19

Потому что:

dlc = 9      -> 00000009
ide = 1<<4   -> 00000010
fi  = AB<<8  -> 0000AB00
------------------------
               0000AB19

Чтение отдельной части

Извлечение dlc

Поле занимает младшие 4 бита.

dlc := v MOD 10H;

Почему это работает?

v = q * 16 + r

где остаток r всегда находится в диапазоне 0..15.

То есть MOD 10H отсекает всё старшее.

Извлечение ide

Сначала сдвигаем значение вправо делением:

ide := v DIV 10H MOD 2;

Или по шагам:

v DIV 10H

перемещает бит ide в младшую позицию.

После этого:

MOD 2

оставляет только его.

Извлечение fi

fi := v DIV 100H MOD 100H;

Здесь:

  • DIV 100H убирает младшие 8 бит;
  • MOD 100H оставляет только следующие 8.

Изменение отдельного поля

Это особенно интересно.

Замена dlc

Нужно:

  1. убрать старое значение;
  2. вставить новое.

Пусть новое значение:

newDlc = 5

Тогда:

v := v - (v MOD 10H);
v := v + 5;

Замена ide

v := v - (v DIV 10H MOD 2) * 10H;
v := v + 1 * 10H;

Замена fi

v := v - (v DIV 100H MOD 100H) * 100H;
v := v + 55H * 100H;

Более общий вариант

Вся схема основана на позиционной записи числа.

Фактически это работа с цифрами числа в системе счисления.

Для двоичных полей основание равно:

2^ширина_поля

Например:

Размер поля Основание
1 бит 2
4 бита 16
8 бит 256

Поэтому:

MOD base

извлекает поле, а

DIV base

сдвигает к нему.

Главное достоинство: произвольные диапазоны

И тут начинается самое интересное.

Битовые поля жёстко привязаны к степеням двойки.

Но арифметическая упаковка работает вообще с любыми диапазонами.

Например:

секунда  : 0..59
минута   : 0..59
час      : 0..23

Можно хранить время одним числом.

Упаковка

v := sec
  + min * 60
  + hour * 60 * 60;

Извлечение

Секунды:

sec := v MOD 60;

Минуты:

min := v DIV 60 MOD 60;

Часы:

hour := v DIV (60 * 60) MOD 24;

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

Почему такой подход часто лучше

Переносимость

Код не зависит от:

  • endian;
  • ABI;
  • компилятора;
  • порядка битовых полей.

Явность

Все смещения и диапазоны описаны явно.

Нет скрытой магии компилятора.

Универсальность

Работает:

  • для битов;
  • для десятичных цифр;
  • для любых диапазонов;
  • для сериализации;
  • для компактных идентификаторов;
  • для кодирования состояний.
oberon/intpack.txt · Последнее изменение: 2026/05/21 17:11 — comdiv