Объединение переменных и битовые поля как в С++ UNION

В С++ есть такой UNION объединение, который позволяет объединять несколько типов, или так сказать закреплять общую память под разными переменными.

Как такое сделать на Паскаль или PascalABC ?

//===================================================================

 union Ued { //определяю объединяющий память тип, оператором union.
	 Byte bb; // переменная bb которая будет иметь общую память с структурой bayt.
	 struct { //структура bayt которая будет иметь общую память с переменной bb.
	 	unsigned b0 : 1;  //создаю переменную бит, 1- это выделение одного бита под переменную. 
	 	unsigned b1 : 1; //всего восемь одинаковых переменных в структуре для соответствия размеру байта.
	 	unsigned b2 : 1;
	 	unsigned b3 : 1;
	 	unsigned b4 : 1;
	 	unsigned b5 : 1;
	 	unsigned b6 : 1;
	 	unsigned b7 : 1;
	 }bayt; //имя структуры.
 }; 

//=======================================================================

//использование:

Ued un; // создаю объединенную переменную.

un.bb = 12; //заношу число в переменную типа байт.

richTextBox1->AppendText("\r\n" + " bb = " + un.bb.ToString());

richTextBox1->AppendText("\r\n" + 
	"." + un.bayt.b7 + "." + un.bayt.b6 + "." + un.bayt.b5 + "." + un.bayt.b4
	+ "." + un.bayt.b3 + "." + un.bayt.b2 + "." + un.bayt.b1 + "." + un.bayt.b0);

un.bayt.b5 = 1; //изменяю один бит.

richTextBox1->AppendText("\r\n" + " bb = " + un.bb.ToString());

richTextBox1->AppendText("\r\n" +
	"." + un.bayt.b7 + "." + un.bayt.b6 + "." + un.bayt.b5 + "." + un.bayt.b4
	+ "." + un.bayt.b3 + "." + un.bayt.b2 + "." + un.bayt.b1 + "." + un.bayt.b0);

//=======================================================================

результат:

bb = 12

bayt = .0.0.0.0.1.1.0.0

bb = 44

bayt = .0.0.1.0.1.1.0.0 //===================================================================

Никак, а вот в PascalABC.Net можно через атрибуты, ниже покажу.

Ну, во-первых у вас каждая переменная имеет тип uint32, то есть они занимают 4*8 или 32 байта. А во-вторых, это вроде ни в C++, ни в .Net невозможно - выбирать позицию в структуре с бОльшей точностью чем 1 байт. bool тоже занимает в памяти 8 бит.

А теперь к тому как это можно реализовать:

uses System.Runtime.InteropServices;

type
  [StructLayout(LayoutKind.&Explicit)]
  r1 = record
    [FieldOffset(0)] i: integer;
    
    [FieldOffset(0)] b1: byte;
    [FieldOffset(1)] b2: byte;
    [FieldOffset(2)] b3: byte;
    [FieldOffset(3)] b4: byte;
    
  end;

begin
  var a: r1;
  a.i := $11223344;
  a.b1.ToString('X').Print;
  a.b2.ToString('X').Print;
  a.b3.ToString('X').Print;
  a.b4.ToString('X').Print;
end.

Но, как видите, указывать можно только номер байта от начала записи. А раскладывание на биты - на сколько я знаю не входит в стандартные методы процессоров. Это всё равно надо делать как минимум 2 операциями:

var b: byte;
var bi := b shr biid and 1;

Тут biid - номер бита от 0 до 7. Я использовал побитовый сдвиг чтоб поставить нужный бит в позицию нижнего бита и далее применил маску (and 1) которая отсекла все остальные биты.

Зря Вы так. В плюсах с некоторыми ограничениями - можно.

Пример юзкейса - протоколы сетевого взаимодействия по медленным и нестабильным каналам связи (радиомодемы, телефонная линия со скоростью около 1200 бод), которые вынуждают паковать данные, передаваемые в пакете, максимально эффективно.

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

Понятно… Ну, на стак оверфлоу есть про это вопрос и там аналогов не предложили.

Есть конечно System.Collections.BitArray, но он тоже работает через побитовые сдвиги.

Во всех паскалях, кроме PascalABC.NЕТ, имеется опция absolute, организующая “перекрытие” данных. В PascalABC.NЕТ не реализовано, насколько я понял, из-за особенностей работы с указателями в .NET. Моделируется сдвигами и AND с нужной маской.

Судя по ссылке, данная опция не предоставляет возможности получения переменной, расположенной “внутри” другой переменной (например, uint32_t с 3го по 7 бит внутри uint64_t). Слабоватая опция. Смотрится, честно говоря, как кривой способ объявить ссылку на переменную. А есть известные живые примеры правильного использования?

1 лайк

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

Благодарю за пример. вы как всегда даете интересные варианты. А, а на счет моего примера так он рабочий на все сто в VisualStudio 2017 C++.

Так оно не реализовано в PascalABC.NЕТ. А как было - найдете в любой книге по Pascal.

К стати, это не смещение: 

    [FieldOffset(1)] b2: byte;
    [FieldOffset(2)] b3: byte;

 (1) и (2) это идентификаторы под которыми переменные будут иметь общую память.

К примеру, если написать:

    type
    [StructLayout(LayoutKind.&Explicit)]
 //Для ручного расположения полей в памяти используется атрибут StructLayout.
//значение Explicit, позволяет указать точный размер поля по размеру его типа. 
      ed = record
     [FieldOffset(1)] aa: byte;
     [FieldOffset(22)] bb: byte;
     [FieldOffset(1)] cc: byte;
     [FieldOffset(22)] ee: byte;
    end;

// - то переменные aa = cc; bb=ee;

Ну как это не смешение? Вы для начала потестируйте нормально.

  1. Запустите мой пример, там ясно видно что integer разбивает на 4 отдельных байта, в порядке их размещения в памяти.
uses System.Runtime.InteropServices;

type
  [StructLayout(LayoutKind.&Explicit)]
  r1 = record
    
    [FieldOffset(23)] b1: byte;
    [FieldOffset(34)] b2: byte;
    
  end;

begin
  var a: r1;
  var p1 := @a.b1;
  var p2 := @a.b2;
  writeln(
    integer(p2)-integer(p1)//11, потому что 34-23
  );
end.
  1. Хотя бы на msdn уже почитали бы, там тоже ясно сказано что это именно позиция в памяти, относительно начала записи.

И это что должно значить? Размер поля не меняется, меняется только расположение.

И ещё, строчки содержащие ``` не должны содержать больше ничего, иначе они будут глючить. Поправьте у себя выделение, пожалуйста.

не ну да это типа смещение << Атрибут FieldOffset позволяет явно определить смещения полей в памяти, что дает возможность создавать объединения в стиле языка C, где поля могут накладываться друг на друга. >> но у меня получилось так как я написал, а вот чтоб наложить две переменные как в С, не получается, в смысле со смещением на один бит.

uses System.Runtime.InteropServices;

type
  [StructLayout(LayoutKind.&Explicit)]
  r1 = record
    
    [FieldOffset(0)] i1: integer;
    [FieldOffset(3)] i2: integer;//1 байт пересекается
    
  end;

begin
  var a: r1;
  a.i1 := $11223344;
  a.i1.ToString('X').Println;
  a.i2 := $55667788;
  a.i1.ToString('X').Println;
end.

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

1 лайк

я так пробовал вашь первый пример, присваивал полям по еденице и по идее должно получится 11= 2, но не получилось, щас еще раз посмотрю может что то нето…))

Я ничего не понял, дайте код.

И да, FieldOffset считает в байтах.

все я понял, там отсчет идет в байтах. в битах не получится.

почему не работает ?

uses  System.Collections;

mas: array of BitArray := new BitArray[8];
mas[1]:=true;

я вот не пойму, если фундамент в программировании БИТЫ, то почему не реализовывать работу с ними в первую очередь и не делать это красиво и просто. Взял массив бит и используешь. И не надо мне объяснять, что память состоит из байт. Ни ужели нужно постоянно писать свою библиотеку используя смещения под эту хрень. Ни ужели нельзя эту библиотеку сделать стандартной для языка, ВЕДЬ ЭТО ФУНДАМЕНТ!!! Или все решают примитивные задачки… Для чего нужно изучать язык программирования, если в нем нельзя просто манипулировать фундаментальными АТОМАМИ ? - по моему это не космос неизвестных физических процессов… тут все известно, и это язык не низкого уровня чтоб я использовал shl - shr в порциях массива байт. Взяли бы да и сделали тип Bit и привязали к ним функции преобразования. Мало ли что поначалу это медленно работать будет из за не совершенных алгоритмов, дальше больше. Ладно лирика, сделали бы хотя бы чтоб тянул то, что уже есть в .NET.

Фундамент в программировании идет всегда от аппаратной платформы, а не наоборот. Иначе будет неэффективно. Аппаратная платформа давно уже байт-ориентированная, более того, платформа ориентирована на некое “машинное слово” - 4, 8 или 16 байт. Работа на уровне отдельных битов за пределами процессора не ведется, только внутри - сдвиги, переносы, да и то там параллельно это происходит. Главное - аппаратная часть компьютера не умеет АДРЕСОВАТЬ биты. И в этом вся причина.

а если так:

uses  System.Collections;

var
    mas: BitArray := new BitArray(8);
    
begin
    mas[1]:=true;
    writeln('count: ', mas.Count);
    writeln('length: ', mas.Length);
    writeln('value[1]: ', mas[1]);
    write('values: ');
    foreach var s in mas do write(s, '  ');
end.