Russian Qt Forum

Программирование => С/C++ => Тема начата: kambala от Апрель 23, 2021, 23:40



Название: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 23, 2021, 23:40
Есть некоторый бинарный формат, в котором данные имеют размер в битах. Синтетический пример: (тип int взят от балды)
Код
C++ (Qt)
struct A {
 int a; // 2 bits
 int b; // 15
};
 
struct B {
 A a;
 int b; // 26
 int c; // 1
 int d; // 53
};
 
struct C {
 int a; // 42
 B b[];
 int c; // 12
};
и так далее. Причем размер не всех полей известен на момент компиляции. Только точно известно, что не бывает полей шире, чем 64 бита.

1) Есть какие-то готовые библиотеки, которые смогут буфер с байтами превратить в заданные структуры? (ну и обратный процесс)
2) как описать эти структуры данных? Хочется чего-то более проверяемого на момент компиляции, чем обычный сишный bitfield. Какие трюки можно использовать на шаблонах/трейтах/концептах? Наверное, в идеале хотелось бы, чтобы правильный тип полю назначался в зависимости от заданной битовой ширины, что-то типа такого:
Код
C++ (Qt)
template<N, S>
struct Field {
 bool v // if N == 1
 int8_t v // if N <= 8 && S=signed
 uint8_t v // if N <= 8 && S=unsigned
 ...
};

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


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 24, 2021, 07:13
Не слышал о таких либах. Если велик-вариант интересен, то

Если  "размер не всех полей известен на момент компиляции", то отделаться bitfield все равно не удастся. Я бы хранил в памяти "нормальные" члены класса (int, short, char), и нормальные вектора (если это не противоречит др требованиям). А для чтения/записи в биты нужно иметь таблицы битовых размеров, возможно мапа с ключом typeof()


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 24, 2021, 07:54
Причем размер не всех полей известен на момент компиляции. Только точно известно, что не бывает полей шире, чем 64 бита.
Т.е. в формате вместе со значением поля указывается и его размер в битах?
Или вы имеете ввиду, что пользователь этого формата должен иметь возможность выбирать любой размер поля от 1-64 бита?

https://github.com/CrustyAuklet/bitpacker
https://github.com/wkaras/C-plus-plus-library-bit-fields


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 24, 2021, 11:41
А вообще тема конечно обширная. Все зависит от того, что вы хотите: если вам нужны только pack/unpack - это будет одно решение. Если вы хотите хранить такие структуры в памяти и работать с ними из бизнес-логики, то другое.

Для примера:
Код
C++ (Qt)
// Схемы пакетов
auto ShemaPacket1 = std::make_tuple( 3, 3, 10, 52 );
auto ShemaPacket2 = std::make_tuple( 9, 11, 37, 60, 1, 1 );
 
// Возвращает упакованный bit_array
auto bits1 = pack( ShemaPacket1, 1, 2, 8, 1024 );
auto bits2 = pack( ShemaPacket1, data.val1, data.val2, data.val3, data.val4 );
auto bits3 = pack( ShemaPacket2, 15, 0, 0, 1024, 1, 0 );
 
// Возвращает кортеж значений по указанной схеме или exception
auto result1 = unpack( bits1, ShemaPacket1 )
auto [val1, val2, val3, val4] = unpack( bits1, ShemaPacket1 )
auto result3 = unpack( bits3, ShemaPacket2 )
 
 


Название: Re: чтение и запись битовых структур данных
Отправлено: AkonResumed от Апрель 24, 2021, 15:58
В плане использования на уровне бизнес-логики - просто наложить placement new:
Код:
// Packed data
struct A {
  int a; // 2 bits
  int b; // 15
};

// State less business-logic class
class UnpackedA
{
public:
int a() const { return *reinterpret_cast<const int*>(buf()) & 0x3; }
int b() const { return *reinterpret_cast<const int*>(buf() + sizeof(int)) & 0x7FFF; }

private:
// Returns buffer of packed data
const char* buf() const { return reinterpret_cast<const char*>(this); }
};

int foo() {
A a;  // e.g. got from network
auto ua = new (&a) UnpackedA;
}

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

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


Название: Re: чтение и запись битовых структур данных
Отправлено: Racheengel от Апрель 26, 2021, 14:09
MessagePack?

https://msgpack.org/


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 26, 2021, 19:30
спасибо! отвечу по порядку.
Не слышал о таких либах. Если велик-вариант интересен, то

Если  "размер не всех полей известен на момент компиляции", то отделаться bitfield все равно не удастся. Я бы хранил в памяти "нормальные" члены класса (int, short, char), и нормальные вектора (если это не противоречит др требованиям). А для чтения/записи в биты нужно иметь таблицы битовых размеров, возможно мапа с ключом typeof()
велик интересен, если ничего пригодного не найдется :)

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

как хранить "нормальные" члены класса не очень понял, ведь точно известно только то, что значение уместится в int64.
Причем размер не всех полей известен на момент компиляции. Только точно известно, что не бывает полей шире, чем 64 бита.
Т.е. в формате вместе со значением поля указывается и его размер в битах?
Или вы имеете ввиду, что пользователь этого формата должен иметь возможность выбирать любой размер поля от 1-64 бита?

https://github.com/CrustyAuklet/bitpacker
https://github.com/wkaras/C-plus-plus-library-bit-fields
да на выделенное. хотелось бы задавать размер поля прямо в описании структуры данных (для наглядности), а пользовательский код будет просто читать это значение при необходимости. Это не сверхкритично, иначе придется просто поместить эти размеры в код (де)сериализации.

вторая ссылка: открыл пдф документацию и... ничего не понял :) файл с тестами тоже понимания особого не принес. также в единственном issue пишут, что
Цитировать
G++ uses more than 3GB of memory (I estimated 6, although I couldn't be sure as the memory monitor stopped working the moment thrashing started). CLang++ only uses up to 1.4 GB of memory although it can take 3-5 minutes too and use up one core fully (ensure you have proper cooling).
что как-то не особо приятно.

первая ссылка: вот тут вменяемая документация :) даже удалось тестовый код написать. но там неприятная штука есть:
Цитировать
little endian byte order not yet implemented for packing/unpacking (yet...)
у меня данные как раз в little endian. для «обычных» чисел можно выкрутиться простым переворотом байт, а вот с битами оказалось чуть сложнее...

пример существующего кода, который «просто работает» (случай big endian архитектур не берем):
Код
C++ (Qt)
static const unsigned int BitsInByte = 8;
 
unsigned long readBits(unsigned char bits, unsigned char *buf, unsigned int *pBufPos)
{
   unsigned int bufPos = *pBufPos, newBufPos = bufPos + bits;
   unsigned int bufIndexOfFirstByte = bufPos / BitsInByte;
   unsigned int ignoreBitsInFirstByte = bufPos % BitsInByte;
   unsigned int fullByteStart = bufPos + (BitsInByte - ignoreBitsInFirstByte);
   unsigned int additionalBits = newBufPos - fullByteStart;
   unsigned int bitsInLastByte = additionalBits - (additionalBits / BitsInByte) * BitsInByte;
   unsigned int bufIndexOfLastByte = (newBufPos - 1) / BitsInByte + (bitsInLastByte ? 0 : 1);
 
   // start with remainder bits of the first byte
   unsigned long value = buf[bufIndexOfFirstByte] >> ignoreBitsInFirstByte;
   // add "full" bytes in between
   for (unsigned int fullByteIndex = fullByteStart / BitsInByte, lshiftBits = BitsInByte - ignoreBitsInFirstByte; fullByteIndex < bufIndexOfLastByte; ++fullByteIndex, lshiftBits += BitsInByte)
       value |= buf[fullByteIndex] << lshiftBits;
   // end with a piece of the last byte
   if (bitsInLastByte)
       value |= (buf[bufIndexOfLastByte] & ((1 << bitsInLastByte) - 1)) << (bits - bitsInLastByte);
 
   *pBufPos = newBufPos;
   return value;
}
 
int main()
{
 unsigned char buf[] = {0, 0x1E, 0x40};
 unsigned int pos = 0;
 std::cout << readBits(9, buf, &pos) << ' ' << readBits(13, buf, &pos) << '\n'; // 0 15
 return 0;
}
получить то же самое на этой либе получилось лишь с помощью переворота всех битов в буфере и с последующим чтением с LSB:
Код
C++ (Qt)
int main()
{
 std::array c = {std::byte{0}, std::byte{0x78}, std::byte{0x2}}; // swapped
 auto u = bitpacker::unpack(BP_STRING("<u9u13"), c); // < означает LSB
 std::cout << std::get<0>(u) << ' ' << std::get<1>(u) << '\n';
 return 0;
}
придется или ждать пока автор допилит поддержку LE (хотя уже год не было коммитов, только в другой ветке, но там другая тема) или самому ее добавлять (для начала разобрать этот супершаблонный код) или писать велик с блэкджеком и шлюхами :)

для полей неизвестной ширины надо будет пользоваться функциями get / store (а может и известные тоже проще через эти функции), т.к. все равно надо будет делать обертки для порядка полей / автоматического сдвига счетчика.
А вообще тема конечно обширная. Все зависит от того, что вы хотите: если вам нужны только pack/unpack - это будет одно решение. Если вы хотите хранить такие структуры в памяти и работать с ними из бизнес-логики, то другое.
думаю, что лучше иметь сырые битовые данные в одних структурах («сырых»), которые и будут читаться/писаться из/в файл, а бизнес-логику на других, более понятных структурах, которые будут конвертироваться из/в «сырые» структуры.
В плане использования на уровне бизнес-логики - просто наложить placement new:

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

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

https://msgpack.org/

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

сделал еще небольшую попытку повелосипедить (до того, как начал смотреть те библиотеки).
Код
C++ (Qt)
// C++17
#include <type_traits>
#include <iostream>
 
struct I
{
 virtual int64_t value() const = 0;
 virtual void print() const = 0;
};
 
template<int N>
struct A : public I
{
 static constexpr auto size = N;
 
 std::conditional_t<size == 1, bool,
   std::conditional_t<size <= 8, char, int>
 > v;
 
 A(decltype(v) vv) : v(vv) {}
 
 int64_t value() const override { return v; }
 void print() const override { std::cout << size << ' ' << std::boolalpha << v << '\n'; }
};
 
int main()
{
 A<1> a{true};
 A<8> b{'!'};
 A<10> c{42};
 
 static_assert(a.size == 1);
 static_assert(std::is_same_v<decltype(a.v), bool>);
 static_assert(std::is_same_v<decltype(b.v), char>);
 static_assert(std::is_same_v<decltype(c.v), int>);
 
 for (auto const i : std::initializer_list<I const *>{&a, &b, &c})
 {
   std::cout << i->value() << '\t';
   i->print();
 }
 
 return 0;
}
работает, но мне кажется можно как-то получше :) потом еще заметил, что в BitPacker тот же трюк используется:
Код
C++ (Qt)
       /// finds the smallest fixed width unsigned integer that can fit NumBits bits
       template <size_type NumBits>
       using unsigned_type = std::conditional_t<NumBits <= 8, uint8_t,
               std::conditional_t<NumBits <= 16, uint16_t,
                       std::conditional_t<NumBits <= 32, uint32_t,
                               std::conditional_t<NumBits <= 64, uint64_t,
                                       void >>>>;
 
       /// finds the smallest fixed width signed integer that can fit NumBits bits
       template <size_type NumBits>
       using signed_type = std::conditional_t<NumBits <= 8, int8_t,
               std::conditional_t<NumBits <= 16, int16_t,
                       std::conditional_t<NumBits <= 32, int32_t,
                               std::conditional_t<NumBits <= 64, int64_t,
                                       void >>>>;

P.S. вопрос знатокам :) как задать массив (обычный или std::array) из std::byte по-человечески? у меня получается только с явным вызовом конструктора для каждого элемента как в коде выше. ([unsigned] char предлагать не надо :) )
Код
C++ (Qt)
std::array c = {std::byte{0}, std::byte{0x78}, std::byte{0x2}};


Название: Re: чтение и запись битовых структур данных
Отправлено: juvf от Апрель 27, 2021, 06:53
1) Есть какие-то готовые библиотеки, которые смогут буфер с байтами превратить в заданные структуры? (ну и обратный процесс)
Я делаю через юнион

Код:
struct LCanOpen
{
union
{
uint64_t data;
struct
{
int16_t x;
int16_t y;
int16_t rezerv;
struct
{
uint16_t nx :2;
uint16_t ny :2;
uint16_t rezerv1 :12;
};
};
};
};

struct Bjm
{
union
{
uint64_t data;
struct
{
uint64_t neutralX :2;
uint64_t left :2;
uint64_t right :2;
uint64_t x :10;
uint64_t neutralY :2;
uint64_t back :2;
uint64_t forward :2;
uint64_t y :10;
uint64_t rezerv :8;
uint64_t but4 :2;
uint64_t but3 :2;
uint64_t but2 :2;
uint64_t but1 :2;
uint64_t but8 :2;
uint64_t but7 :2;
uint64_t but6 :2;
uint64_t but5 :2;
uint64_t but12 :2;
uint64_t but11 :2;
uint64_t but10 :2;
uint64_t but9 :2;
};
};
};

При получении данных я не распихиваю каждый битик сдвигами в нужное поле, а сразу весь буфер помещаю в data

Код:
uint64_t recData;
uint8_t recBuf[100];

recive(port1, &recData);
recive(port2, recBuf);

struct Bjm bjm;
struct LCanOpen can;

bjm.data = recData;//заполенение структуры простым "="
memcpy(&can.data, &recbuf[13], 8); //заполнение структуры, в случае, если принятые данные не выровнены.

recive(port1, &can.data);
//обратный процесс
*(uint64_t*)recBuf = bjm.data;//опасно, но быстро.
//или
memcpy(recBuf+13, &bjm.data, 8);//безопасно
Такими юнионами легко одним "=" можно получить заполнить Bjm.x, который состоит из 10 бит и разбросан в 2-х байтах. Доступ

Код:
unsigned int x = bjm.x;
unsigned int y = can.ny


зы только сейчас заметил "Причем размер не всех полей известен на момент компиляции". Мой вариант тут наверно не подойдет.



Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 08:35
как хранить "нормальные" члены класса не очень понял, ведь точно известно только то, что значение уместится в int64.
"Нормальные" имелось ввиду что все песни с битами только для I/O, а так нормальные структуры. Пока неясно нужно ли битовое представление в памяти. Даже если "иногда нужно" то может проще использовать ф-ции I/O на лету, расходы невелики. Уточните этот момент чтобы не гонять воздух.

Если "только I/O" то надо заряжать какой-то CBitStream и операторы << и >> для каждой структуры, собсно это все.

велик интересен, если ничего пригодного не найдется
"Готовые проверенные" хороши для типовых, хорошо изученных задач. Отсюда частенько делается вывод что, мол, для всего , и "все уже давно написано" :) Но стоит задаче быть просто "не такой уж популярной" - и число "готового" резко падает, а с "проверенным" проблемы. Напр здесь найденное особого впечатления не производит, хотя возможно и пригодно.


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 27, 2021, 08:47
Отсюда частенько делается вывод что, мол, для всего , и "все уже давно написано" :)
Вы уже много лет делаете этот ошибочный вывод. Это не так. :)
Но есть много проектов с интересными идеями, которые могут подтолкнуть. Не нужно тащить к себе и пытаться использовать все подряд, а просмотр может оказаться полезным.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 09:28
Да, если нужны "и так и сяк", то есть приятная специфика - влазит в 64 бита. Напрашивается
Код
C++ (Qt)
struct A {
 int m_B;
 int m_C;
 ...
 qint64 m_bits;
}
И нахрюкать сеттеров которые обновляют m_bits;


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 09:51
Я делаю через юнион
писать в одно поле юниона, а читать из другого законно только в С, в С++ же (https://en.cppreference.com/w/cpp/language/union#Explanation):
Цитировать
It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.
оно может и работает в clang/gcc/msvc, но полагаться на это не хочется :)
как хранить "нормальные" члены класса не очень понял, ведь точно известно только то, что значение уместится в int64.
"Нормальные" имелось ввиду что все песни с битами только для I/O, а так нормальные структуры. Пока неясно нужно ли битовое представление в памяти.
нет, это только для I/O:
думаю, что лучше иметь сырые битовые данные в одних структурах («сырых»), которые и будут читаться/писаться из/в файл, а бизнес-логику на других, более понятных структурах, которые будут конвертироваться из/в «сырые» структуры.
Да, если нужны "и так и сяк", то есть приятная специфика - влазит в 64 бита. Напрашивается
Код
C++ (Qt)
struct A {
 int m_B;
 int m_C;
 ...
 qint64 m_bits;
}
И нахрюкать сеттеров которые обновляют m_bits;
в 64 бита влезает одно поле, а структуры там побольше будут


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 10:24
нет, это только для I/O:
Тогда не вижу в чем проблема, и зачем что-то искать. Пишем операторы потока для каждого класса - и все дела

оно может и работает в clang/gcc/msvc, но полагаться на это не хочется
Ну хорошо хоть так :) Не первый раз вижу это, и обычно делается вывод типа "юнионы не работают, применять их низзя" - таков обычный итог широких познаний :)

в 64 бита влезает одно поле, а структуры там побольше будут
Ну пришлось бы разориться на array или vector, смысл тот же


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 10:50
Тогда не вижу в чем проблема, и зачем что-то искать. Пишем операторы потока для каждого класса - и все дела
вот мы и пришли к вопросам из первого поста :)

1) велосипед или готовое (частично ответили)
2) как сделать ширину поля частью структуры данных, а не частью парсера (попытки выше есть, комментариев к ним пока нет)


Название: Re: чтение и запись битовых структур данных
Отправлено: juvf от Апрель 27, 2021, 11:11
писать в одно поле юниона, а читать из другого законно только в С, в С++ же (https://en.cppreference.com/w/cpp/language/union#Explanation):
Цитировать
It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.
оно может и работает в clang/gcc/msvc, но полагаться на это не хочется :)
Писать в одно поле юниона, а читать из другово (в моих примерах) законно в любом языке. То, о чем говориться по вашей ссылке к моему случаю не имеет отношения.

на поальцах, про эту граблю.... если
Код:
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};
то записали в n одно, а читаем из с - получаем It's undefined behavior to read. Потому что sizeof(c) < sizeof(n).
Если будет
Код:
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint32_t c;     // occupies 4 byte
    float m;     // occupies 4 byte
    MyType g; // occupies 4 byte
};
То ни каких undefined behavior to read не случиться. Вы можете безопасно писать  и читать в любой член юниона. Потому что размер S равен hold its largest data member. А размер любого member == largest data member и == размеру S.

В моем случае примере под юнионом объединены два члена: uint64_t и struct (битовое поле). Каждый член занимает 8 байт.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 11:35
2) как сделать ширину поля частью структуры данных,
Не уверен что стоит ставить такую цель. Напр
Код
C++ (Qt)
struct A {
 int m_B;
 short m_C;
 ...
 char m_Z;
};
Если заменить int, short и.т.п. на нечто "содержащее битовый размер" (а это темплейт, другого не видно) это изрядно загадит текст, во всяком случае оперировать с членами будет трудно. Не применить ли KISS-прынцып (столь любимый некоторыми из нас  :)). Простецкий вариант
Код
C++ (Qt)
void A::ReadWrite( CBitStream & stream, TMode mode )
{
 stream.ReadWriteInt(&m_B, 4, mode);    
 stream.ReadWriteShort(&m_C, 12, mode);
 ...
 stream.ReadWriteChar(&m_Z, 7, mode);
}
Он не так уж плох и работать будет.


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 27, 2021, 11:50
kambala, поясните пожалуйста, как с таким требованием:

Причем размер не всех полей известен на момент компиляции. Только точно известно, что не бывает полей шире, чем 64 бита.

вы планировали задавать размеры полей здесь?

Код
C++ (Qt)
template<N, S>
struct Field {
 bool v // if N == 1
 int8_t v // if N <= 8 && S=signed
 uint8_t v // if N <= 8 && S=unsigned
 ...
};

Устанавливать максимальным? Тогда теряется смысл с наглядностью.


Название: Re: чтение и запись битовых структур данных
Отправлено: Авварон от Апрель 27, 2021, 12:49
Писать в одно поле юниона, а читать из другово (в моих примерах) законно в любом языке. То, о чем говориться по вашей ссылке к моему случаю не имеет отношения.

Нет, не законно=) Это опять возвращает нас к разговору о reinterpret_cast и type punning.
Единственный законный способ сделать type punning в современном с++ - это memcpy.
В с++20 добавят bit_cast который сейчас можно написать самому через memcpy (https://en.cppreference.com/w/cpp/numeric/bit_cast)

Код:
template <class To, class From>
typename std::enable_if_t<
    sizeof(To) == sizeof(From) &&
    std::is_trivially_copyable_v<From> &&
    std::is_trivially_copyable_v<To>,
    To>
// constexpr support needs compiler magic
bit_cast(const From& src) noexcept
{
    static_assert(std::is_trivially_constructible_v<To>,
        "This implementation additionally requires destination type to be trivially constructible");
 
    To dst;
    std::memcpy(&dst, &src, sizeof(To));
    return dst;
}

То есть можно скопировать буфер в структуру через memcpy а вот записать int64_t а потом прочитать из битфилда на него наложеннный - нет.
Но да, все известные мне компиляторы это позволяют делать. Но clang-tidy ругается AFAIK


Название: Re: чтение и запись битовых структур данных
Отправлено: juvf от Апрель 27, 2021, 13:08
То есть можно скопировать буфер в структуру через memcpy а вот записать int64_t а потом прочитать из битфилда на него наложеннный - нет.
Но да, все известные мне компиляторы это позволяют делать. Но clang-tidy ругается AFAIK
А в чем косяк такого метода? Я понимаю когда объединили int и long. Но когда int и uint uint32_t и float - за каким углом, в какой архитектуре и с каким компилятором может быть подвох?

Это не то, чтобы "в с/с++ это предусмотрено и так нужно делать", это безопасный лайфхак. Если размер битовой структры 64 бита, и объединённого члена 64 бита, то компилятор не то что не позволит это сделать, или что-то испортит - а куда он денется с подводной лодки?


Название: Re: чтение и запись битовых структур данных
Отправлено: Авварон от Апрель 27, 2021, 13:15

Это не то, чтобы "в с/с++ это предусмотрено и так нужно делать", это безопасный лайфхак. Если размер битовой структры 64 бита, и объединённого члена 64 бита, то компилятор не то что не позволит это сделать, или что-то испортит - а куда он денется с подводной лодки?

все ненавистное UB нужно для оптимизаций.
Можно тот пример с reinterpret_cast переписать на union:

Код:
union U {
    int i{0};
    float f{0};
};

int foo()
{
        U u;
u.i = -1;
u.f = 0.0f;

return u.i; // 0 или -1?
}


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 13:17
Не применить ли KISS-прынцып (столь любимый некоторыми из нас  :)). Простецкий вариант
сейчас у меня примерно так и сделано, но не считаю, что это хорошее решение, надо попробовать поискать получше :)
kambala, поясните пожалуйста, как с таким требованием:

вы планировали задавать размеры полей здесь?

Устанавливать максимальным? Тогда теряется смысл с наглядностью.
тот пример — для известных размеров. для неизвестных, думаю, надо немножко другой контейнер, который позволит задавать ширину поля в конструкторе. подлежащий тип будет максимальным, да. наглядности с неизвестными не достичь, конечно :)


Название: Re: чтение и запись битовых структур данных
Отправлено: juvf от Апрель 27, 2021, 13:24
Цитировать
0 или -1?
0

Цитировать
type punning
понятно... да это вариант с type punning


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 15:24
тот пример — для известных размеров. для неизвестных, думаю, надо немножко другой контейнер, который позволит задавать ширину поля в конструкторе. подлежащий тип будет максимальным,
"Неизвестный" = содержит массив(ы) переменной длины, счетчик должен читаться/писаться в поток. Или есть др варианты?


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 15:27
Цитировать
0 или -1?
ответом будет:
Код:
untitled 2:4:11: error: initializing multiple members of union
    float f{0};
          ^
untitled 2:3:9: note: previous initialization is here
    int i{0};
        ^
1 error generated.
:D

предлагаю вам ваше обсуждение об играх с юнионом перенести в другой тред :)
"Неизвестный" = содержит массив(ы) переменной длины, счетчик должен читаться/писаться в поток. Или есть др варианты?
не понял, счетчик чего?

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


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 15:28
P.S. вопрос знатокам :) как задать массив (обычный или std::array) из std::byte по-человечески? у меня получается только с явным вызовом конструктора для каждого элемента как в коде выше. ([unsigned] char предлагать не надо :) )
Код
C++ (Qt)
std::array c = {std::byte{0}, std::byte{0x78}, std::byte{0x2}};
нашел ответ (https://stackoverflow.com/a/46151026/1971301): это так бай дизайн. лучше тогда брать uint8_t.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 16:02
не понял, счетчик чего?
Счетчик эл-тов массива/контейнера - члена класса
неизвестный на момент компиляции, в рантайме читается из конфигурационных файлов (они всегда доступны).
А существующие файлы записанные с "конфигурацией X"? Выходит в поток все равно надо писать кол-во бит.
сейчас у меня примерно так и сделано, но не считаю, что это хорошее решение, надо попробовать поискать получше :)
Может смущает просто отсутствие новомодных цацок? :) Ну допустим даже удалось как-то объявить - от (полного) написания почленного I/O это все равно не избавит. И как битовые размеры (автоматом) сплавить для I/O - тоже хз.


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 16:20
Счетчик эл-тов массива/контейнера - члена класса
теперь понял. формат данных уже содержит такое поле.
А существующие файлы записанные с "конфигурацией X"? Выходит в поток все равно надо писать кол-во бит.
да, именно с такой конфигурацией. задачи конвертации между конфигурациями не стоит (но в принципе сделать можно).
Может смущает просто отсутствие новомодных цацок? :) Ну допустим даже удалось как-то объявить - от (полного) написания почленного I/O это все равно не избавит. И как битовые размеры (автоматом) сплавить для I/O - тоже хз.
отсутствие не смущает, но не вижу ничего плохого в том, чтобы исследовать что в них есть и как ими можно воспользоваться для улучшения кода, уменьшения писанины и увеличения наглядности/читабельности :)

надеюсь схитрить как-то через массив, задающий порядок полей, что-то типа:
Код:
for field in mystruct.orderedFields
  stream.write(field.size, field.value);
вроде бы на момент компиляции нельзя собрать массив полей структуры/класса? (без кодогенерации)


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 27, 2021, 16:39
отсутствие не смущает, но не вижу ничего плохого в том, чтобы исследовать что в них есть и как ими можно воспользоваться для улучшения кода, уменьшения писанины и увеличения наглядности/читабельности :)
Да, я иногда тоже так рыпаюсь, пусть без особого успеха - но интересно
надеюсь схитрить как-то через массив, задающий порядок полей, что-то типа:
Код:
for field in mystruct.orderedFields
  stream.write(field.size, field.value);
вроде бы на момент компиляции нельзя собрать массив полей структуры/класса? (без кодогенерации)
Категорически утверждать "нельзя" не буду, но если и можно, то там такое мракобесие (https://habr.com/ru/post/344206/) , что ну его нафиг, ото тупенько почленно расписать - сохранить и голову и время


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 27, 2021, 19:07
Категорически утверждать "нельзя" не буду, но если и можно, то там такое мракобесие (https://habr.com/ru/post/344206/) , что ну его нафиг, ото тупенько почленно расписать - сохранить и голову и время
спасибо, попробую. необязательно разбирать шаблоны сверхразума внутри библиотеки, если она решает требуемую задачу :)


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 27, 2021, 20:40
А давайте попробуем придумать, как удобно можно описывать и использовать такие битовые структуры.
Вот один из возможных вариантов, так сказать для затравки (здесь пока все размеры полей задаются при компиляции):
Код
C++ (Qt)
 struct record
 {
   bit_field<1> flag;
   bit_field<4> mode;
   bit_field<9> address;
 
   bit_array pack() const
   {
     return ::pack( flag, mode, address );
   }
 
   static record  unpack( const bit_array &src )
   {
     record result;
     ::unpack( src, result.flag, result.mode, result.address );
     return result;
   }
 };
 


Название: Re: чтение и запись битовых структур данных
Отправлено: juvf от Апрель 28, 2021, 06:43
Цитировать
0 или -1?
ответом будет:
Код:
untitled 2:4:11: error: initializing multiple members of union
    float f{0};
          ^
untitled 2:3:9: note: previous initialization is here
    int i{0};
        ^
1 error generated.
:D
пффф.... вопрос - что вернёт функция? Функция вернёт 0. А ваша ошибка компиляции из-за инициализации юниона, а не из-за использования. К использованию иниона отношения не имеет... ещё пропустите в примере символ ";" и на основании ошибки компиляции отправте юнион в топку. 

union U
{
    int i{0};
    float f;
};
Тут нет ошибки.


Название: Re: чтение и запись битовых структур данных
Отправлено: ssoft от Апрель 28, 2021, 07:21
На эту тему писал свой велик. Продукт проприетарный, поэтому пока опишу только сами подходы.

Реализовывалась работа со структурами специфичного формата данных http://www.eurocontrol.int/services/asterix
Формат данных описывается в параметрическом виде, например, xml

Код
XML
   <DataItem id="020">
       <DataItemName>Target Report Descriptor</DataItemName>
       <DataItemDefinition>Type and characteristics of the radar data as transmitted by a radar station.</DataItemDefinition>
       <DataItemFormat desc="Variable length Data Item comprising a first part of oneoctet, followed by one-octet extents as necessary.">
           <Variable>
               <Fixed length="1">
                   <Bits bit="8">
                       <BitsShortName>TYP</BitsShortName>
                       <BitsValue val="0">Plot</BitsValue>
                       <BitsValue val="1">Track</BitsValue>
                   </Bits>
                   <Bits bit="7">
                       <BitsShortName>SIM</BitsShortName>
                       <BitsValue val="0">Actual plot or track</BitsValue>
                       <BitsValue val="1">Simulated plot or track</BitsValue>
                   </Bits>
                   <Bits from="6" to="5">
                       <BitsShortName>SSRPSR</BitsShortName>
                       <BitsValue val="0">No detection</BitsValue>
                       <BitsValue val="1">Sole primary detection</BitsValue>
                       <BitsValue val="2">Sole secondary detection</BitsValue>
                       <BitsValue val="3">Combined primary and secondary detection</BitsValue>
                   </Bits>
                   <Bits bit="4">
                       <BitsShortName>ANT</BitsShortName>
                       <BitsValue val="0">Target report from antenna 1</BitsValue>
                       <BitsValue val="1">Target report from antenna 2</BitsValue>
                   </Bits>
                   <Bits bit="3">
                       <BitsShortName>SPI</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Special Position Identification</BitsValue>
                   </Bits>
                   <Bits bit="2">
                       <BitsShortName>RAB</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Plot or track from a fixed transponder</BitsValue>
                   </Bits>
                   <Bits bit="1" fx="1">
                       <BitsShortName>FX</BitsShortName>
                       <BitsValue val="0">End of Data Item</BitsValue>
                       <BitsValue val="1">Extension into first extent</BitsValue>
                   </Bits>
               </Fixed>
               <Fixed length="1">
                   <Bits bit="8">
                       <BitsShortName>TST</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Test target indicator</BitsValue>
                   </Bits>
                   <Bits from="7" to="6">
                       <BitsShortName>DS1DS2</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Unlawful interference (code 7500)</BitsValue>
                       <BitsValue val="2">Radio-communication failure (code 7600)</BitsValue>
                       <BitsValue val="3">Emergency (code 7700)</BitsValue>
                   </Bits>
                   <Bits bit="5">
                       <BitsShortName>ME</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Military emergency</BitsValue>
                   </Bits>
                   <Bits bit="4">
                       <BitsShortName>MI</BitsShortName>
                       <BitsValue val="0">Default</BitsValue>
                       <BitsValue val="1">Military identification</BitsValue>
                   </Bits>
                   <Bits from="3" to="2">
                       <BitsShortName>spare</BitsShortName>
                       <BitsName>spare bits set to 0</BitsName>
                       <BitsConst>0</BitsConst>
                   </Bits>
                   <Bits bit="1" fx="1">
                       <BitsShortName>FX</BitsShortName>
                       <BitsValue val="0">End of Data Item</BitsValue>
                       <BitsValue val="1">Extension into next extent</BitsValue>
                   </Bits>
               </Fixed>
           </Variable>
       </DataItemFormat>
   </DataItem>
 
   <DataItem id="040">
       <DataItemName>Measured Position in Polar Coordinates</DataItemName>
       <DataItemDefinition>Measured position of an aircraft in local polar coordinates.</DataItemDefinition>
       <DataItemFormat desc="Four-octet fixed length Data Item.">
           <Fixed length="4">
               <Bits from="32" to="17">
                   <BitsShortName>RHO</BitsShortName>
                   <BitsUnit max="512" scale="0.0078125">NM</BitsUnit>
               </Bits>
               <Bits from="16" to="1">
                   <BitsShortName>THETA</BitsShortName>
                   <BitsUnit scale="0.0054931640625">deg</BitsUnit>
               </Bits>
           </Fixed>
       </DataItemFormat>            
   </DataItem>
 
 

Для этого файла пишется парсер, который формирует структуры с описанием формата данных.

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

Если требуется работать с динамически изменяющимися структурами, то доступ к полям в runtime осуществляется по имени примерно так

Код
C++ (Qt)
record.item( "040" ).field( "RHO" );
 

Если состав структуры известен на этапе компиляции, то по формату xml можно сгенерировать исходники, как предлагал Old или так

Код
C++ (Qt)
 
struct Record
{
   Bytes m_bytes;
 
   // BitField< Byte number,  First bit, Last bit >
   BitField<0, 0, 1> flag;
   BitField<0, 1, 5> mode;
   BitField<0, 5, 14> address;
 
   Record () : flag( m_bytes ), mode( m_bytes ), address( m_bytes ) {}
};
 



Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 28, 2021, 10:37
необязательно разбирать шаблоны сверхразума внутри библиотеки, если она решает требуемую задачу :)
Это довольно скользкое место :) Если либа достаточно представительна, авторитетна - то да, можно просто "полагаться". А иначе...

Но главное не это. Не скрою, начало статьи (пример: печать любой, произвольной структуры) впечатляет. Но если немного поразмыслить, то применимость этого - ну, мягко говоря, невелика. Обычно десериализация выглядит так
Код
C++ (Qt)
void A::Read( Stream & strem )
{
 int version = stream.ReadInt();
 
 m_A = stream.ReadInt();
 m_B = stream.ReadInt();
 
 if (version > 1) {
  ...
 
Т.е. заливка из потока - вовсе не "однообразный цикл" по членам. Находятся какие-то подробности которые в общий механизм не ложатся. Пусть их не так уж много, но без них не обойтись. Именно эта "соломинка" часто ломает спину темплейтовскому верблюду  :)

Код
C++ (Qt)
 
struct Record
{
   Bytes m_bytes;
 
   // BitField< Byte number,  First bit, Last bit >
   BitField<0, 0, 1> flag;
   BitField<0, 1, 5> mode;
   BitField<0, 5, 14> address;
 
   Record () : flag( m_bytes ), mode( m_bytes ), address( m_bytes ) {}
};
 
Это оправдано если есть такая задача :) А делать так только для I/O - явный перебор


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Апрель 29, 2021, 13:35
А давайте попробуем придумать, как удобно можно описывать и использовать такие битовые структуры.
Вот один из возможных вариантов, так сказать для затравки (здесь пока все размеры полей задаются при компиляции):
Код
C++ (Qt)
 struct record
 {
   bit_field<1> flag;
   bit_field<4> mode;
   bit_field<9> address;
 
   bit_array pack() const
   {
     return ::pack( flag, mode, address );
   }
 
   static record  unpack( const bit_array &src )
   {
     record result;
     ::unpack( src, result.flag, result.mode, result.address );
     return result;
   }
 };
 
примерно так и представляется. но хотелось бы избавиться от необходимости явного перечисления полей (пока берем идеальный вариант, где нет никаких записей кусками по условиям, хотя их тоже можно нарезать на более мелкие структуры).

вариант 1 (был выше):
Код:
for field in mystruct.orderedFields
  stream.write(field.size, field.value);
где orderedFields — массив ссылок/указателей на необходимые поля.

вариант 2: заставить кого-то типа Boost.pfr или Boost.Hana (в последней пока ничего не понял из просмотра документации :) ) сформировать порядок полей из описания структур в момент компиляции.
На эту тему писал свой велик. Продукт проприетарный, поэтому пока опишу только сами подходы.
спасибо. по сути, описываем структуру во внешнем файле и генерируем для нее парсер и С++ структуры данных. насколько я понимаю, генератор не содержит ни одного зашитого поля, все читается из внешнего файла — вполне хороший способ! хоть и немного громоздкий :)

но если удастся описать структуру полностью в рамках С++, то и кодогенератор не понадобится.
Т.е. заливка из потока - вовсе не "однообразный цикл" по членам. Находятся какие-то подробности которые в общий механизм не ложатся. Пусть их не так уж много, но без них не обойтись. Именно эта "соломинка" часто ломает спину темплейтовскому верблюду  :)
так можно ж комбинировать: «магические шаблоны» делают свою работу, нестандартные вещи дописать руками.


Название: Re: чтение и запись битовых структур данных
Отправлено: Old от Апрель 29, 2021, 15:12
вариант 1 (был выше):
Код:
for field in mystruct.orderedFields
  stream.write(field.size, field.value);
где orderedFields — массив ссылок/указателей на необходимые поля.

Наверное такое проще всего реализовать через базовый класс для структур:
Код
C++ (Qt)
class bit_field_base;
 
class bit_record
{
public:
 void reg_field( bit_field_base *field ) { assert( field ); m_orderedFields.push_back( field ); }
 
private:
 std::list<bit_field_base*>  m_orderedFields;
};
 
class bit_field_base
{
public:
 bit_field_base( bit_record &record ) { record.reg_field( this ); }
}
 
 
// =================================================
 
class DataRecord : public bit_record
{
public:
 DataRecord() : m_fld1( *this ), m_fld2( *this ), m_fld3( *this ) {}
 
private:
 bit_field<1> m_fld1;
 bit_field<2> m_fld2;
 bit_field<3> m_fld3;
};
 


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Апрель 29, 2021, 16:24
но хотелось бы избавиться от необходимости явного перечисления полей
..
по сути, описываем структуру во внешнем файле и генерируем для нее парсер и С++ структуры данных.
..
Такие планы должны иметь более достойные цели чем "битовое чтение/запись".


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 05, 2021, 13:58
написал парсер с помощью boost.pfr, пока все выглядит отлично :)

«библиотечный» код:
Код
C++ (Qt)
using BitFieldSizeType = uint8_t;
 
template<typename T>
T parseNumber(gsl::span<uint8_t> const& sp, uint64_t& offset, BitFieldSizeType size)
{
 /*
 auto value = bitpacker::extract<T>(sp, offset, size);
 offset += size;
 return value;
 */

 return readBits(size, sp.data(), &offset); // own impl
}
 
template<BitFieldSizeType Bits>
struct BitField
{
 static constexpr auto size = Bits;
 
 std::conditional_t<size == 1, bool,
   std::conditional_t<size <= 8, uint8_t,
     std::conditional_t<size <= 16, uint16_t,
       std::conditional_t<size <= 32, uint32_t, uint64_t>
     >
   >
 > v;
 
 BitField()
 {
   static_assert(size <= 64);
 }
 
 auto parse(gsl::span<uint8_t> const& sp, uint64_t& offset)
 {
   v = parseNumber<decltype(v)>(sp, offset, size);
   return v;
 }
 
  operator auto() const { return v; }
};
 
template<std::size_t Size, BitFieldSizeType Bits>
struct BitArray
{
 std::array<BitField<Bits>, Size> arr;
 
 auto parse(gsl::span<uint8_t> const& sp, uint64_t& offset)
 {
   for (auto& el : arr)
     el.parse(sp, offset);
   return arr;
 }
};
 
// helper types
using BitFieldBool1 = BitField<1>;
 
template<std::size_t Size, BitFieldSizeType Bits = 8>
using BitString = BitArray<Size, Bits>;

парсер:
Код
C++ (Qt)
// helper types
struct NarrowString
{
 std::string string;
 uint8_t maxCharacters;
 
 NarrowString(uint8_t maxCharacters_) : maxCharacters{maxCharacters_}
 {
   assert(maxCharacters > 0);
   string.reserve(maxCharacters - 1);
 }
 
 auto parse(gsl::span<uint8_t> const& sp, uint64_t& offset)
 {
   for (decltype(maxCharacters) j = 0; j < maxCharacters; ++j)
   {
     auto c = BitField<7>{}.parse(sp, offset);
     if (c == 0)
       break;
     string += c;
   }
   return string;
 }
 
 operator auto() const { return string; }
};
 
template<BitFieldSizeType N>
struct ConditionalBitField
{
 BitField<N> _value;
 std::optional<bool> condition;
 
 ConditionalBitField(decltype(condition) condition_ = std::nullopt) : condition{condition_} {}
 
 std::optional<decltype(_value.v)> parse(gsl::span<uint8_t> const& sp, uint64_t& offset)
 {
   bool shouldReadField = condition ? *condition : BitFieldBool1{}.parse(sp, offset);
   if (!shouldReadField)
     return std::nullopt;
   return _value.parse(sp, offset);
 }
};
 
 
// bit structs
struct BasicBitItem
{
 BitField<16> header;
 BitFieldBool1 isQuest;
 BitField<3> _1;
 BitFieldBool1 isIdentified;
 BitField<5+1> _2;
 BitFieldBool1 isSocketed;
 BitField<2+1+1> _3;
 BitFieldBool1 isEar;
 BitFieldBool1 isStarter;
 BitField<2+1> _4;
 BitFieldBool1 isSimple;
 BitFieldBool1 isEthereal;
 BitFieldBool1 _5;
 BitFieldBool1 isPersonalized;
 BitFieldBool1 _6;
 BitFieldBool1 isRW;
 BitField<5+8+2> _7;
 BitField<3> location;
 BitField<4> whereEquipped;
 BitField<4> column;
 BitField<4> row;
 BitField<3> storage;
};
 
struct ExtendedBitItem
{
 BitField<3> socketablesNumber;
 BitField<32> guid;
 BitField<7> ilvl;
 BitField<4> quality;
 ConditionalBitField<3> variableGraphics;
 ConditionalBitField<11> autoprefix;
};
 
 
template <typename F>
void parseField(F& field, gsl::span<uint8_t> const& sp, uint64_t& i)
{
 field.parse(sp, i);
}
 
template <typename T>
T parse(gsl::span<uint8_t> const& sp, uint64_t& i)
{
 T t;
 boost::pfr::for_each_field(t, [&sp, &i](auto& f) {
   parseField(f, sp, i);
 });
 return t;
}
 
 
int main()
{
 // read file...
 gsl::span sp{buf, fileSize};
 uint64_t offset = 0;
 auto base = parse<BasicBitItem>(sp, offset);
 if (base.isSimple)
   return 0;
 
 auto extended = parse<ExtendedBitItem>(sp, offset);
 
 auto someField = ConditionalBitField<16>{base.isRW}.parse(sp, offset);
 if (base.isPersonalized)
   auto inscribedName = NarrowString{16}.parse(sp, offset);
 // further conditions...
 
 return 0;
}

далее надо поместить распарсенное куда-то и обобщить парсер для чтения и записи.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 05, 2021, 14:29
Код:
// bit structs
struct BasicBitItem
{
  BitField<16> header;
  BitFieldBool1 isQuest;
  BitField<3> _1;
  BitFieldBool1 isIdentified;
  BitField<5+1> _2;
...
Ну хорошо, нарисовали какие-то структуры, с которыми разбираться не один день. Мягко говоря, "для чего"? Чтобы автоматом писались/читались биты? Ну это типа "джин, полей этот цветочек". Может есть какие-то еще цели? Или такой код просто "круче выглядит"?  :)


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 05, 2021, 15:10
просто глядя на такую struct, и человек, и машина (код) сразу понимают суть — по-моему это намного удобнее, чем лезть в код парсера и искать размеры полей. плюс автоматическое определение нужного размера подлежащего типа (будет жрать меньше памяти, чем просто засунуть uint64_t во все поля).


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 06, 2021, 18:15
просто глядя на такую struct, и человек, и машина (код) сразу понимают суть — по-моему это намного удобнее, чем лезть в код парсера и искать размеры полей. плюс автоматическое определение нужного размера подлежащего типа (будет жрать меньше памяти, чем просто засунуть uint64_t во все поля).
Это типа "двигатели на машинки"  ставить, т.е. да, разумно, полезно, но... Не навязываю своего мнения, но достигаемый ф-ционал мне кажется слишком малым чтобы оправдать создание цепочки классов. К тому же является  ли оно "сутью" - довольно спорно. Напр вполне возможно что структуры имеют смысл и без игр с битами. И иметь размеры полей в объявлении = хорошо, но это только для статики.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 07, 2021, 10:26
Попробуем усилить вариант что не гадит темплейтами
Код
C++ (Qt)
void A::ReadWrite( CBitStream & stream, TMode mode )
{
 stream.ReadWriteInt(&m_B, 4, mode);    
 ...
}
Что здесь плохо? Ясно эта "4", которую возможно придется мучительно искать. Завести enum'ы получше, но все-таки не гарантируется что для m_B будет нужный, ошибиться нетрудно, этих enum'ов будет неприятно много. И "4" - только для статики. Тогда может так
Код
C++ (Qt)
usiing TBitKey = std::pair<const std::type_info *, size_t>;
 
template<class Owner, class Member>
int GetBitCount( const Owner & owner, const Member & member )
{
// offsetof ?
 char * base = (char *) &owner;
 char * addr = (char *) &member;
 assert(addr >= base);
 assert(adrr <= base + sizeof(base);
 
 auto it = theGlobalBitsMap.find(TBitKey(&std::typeid(Owner), addr - base));
 assert(it != theGlobalBitsMap.end());
 return it->second;
}
 
// usage
void A::ReadWrite( CBitStream & stream, TMode mode )
{
 stream.ReadWriteBits(&m_B, GetBitCount(*this, m_B), mode);    
 ...
}
Чтобы избавиться от тучи enum'ов. Да, мапу придется заполнять из настроек, ну это неизбежно. Да, легко найти "сколько бит для поля" не получится, но можно напечатать всю мапу. И если нужен "авто-слив" (без почленной сериализации) - его легко сделать с мапой


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 08, 2021, 12:48
для меня ваш код выглядит намного менее понятно, чем темплейты :) typeid так и вовсе никогда в жизни не пользовался (щас бегло посмотрел что это и заметил, что специально для ключа словаря придумали std::type_index, потому что на адрес нельзя закладываться). Если он скрыт в оттестированной библиотеке — буду спокойно пользоваться.

и "авто-слив", и печать, через boost.pfr тоже доступны. а заполнить theGlobalBitsMap без создания объекта не получится, я так понял. то есть выглядит как-то так:
Код
C++ (Qt)
A a;
auto t = std::type_index(std::typeid(A));
theGlobalBitsMap[{t, &a.m_B - &a}] = 6;
и тут прямо напрашиваются макросы для улучшения читабельности :)

сюда тоже нужен макрос, ибо ошибку копипастой сделать очень легко:
Код
C++ (Qt)
stream.ReadWriteBits(&m_B, GetBitCount(*this, m_B), mode);
хотя может получится скрыть за функцией, принимающей указатель на член, может ее и затемплейтить можно, чтоб не вставлять в каждую нужную структуру.

по-моему получается очень много ненужной ручной работы плюс доступность лишь в рантайме.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 08, 2021, 16:05
специально для ключа словаря придумали std::type_index, потому что на адрес нельзя закладываться
Не вижу где написано что адрес может уплыть, но в любом случае type_index смотрится приятнее.

хотя может получится скрыть за функцией, принимающей указатель на член, может ее и затемплейтить можно, чтоб не вставлять в каждую нужную структуру.
Конечно, напр так
Код
C++ (Qt)
template<class Owner, class Member>
void SetBitsCount( const Owner & owner, const Member & member, int bitsCount );

по-моему получается очень много ненужной ручной работы плюс доступность лишь в рантайме.
Ручная работа = регистрации каждого поля что пишется в биты (одна строка SetBitsCount). Выгода (по сравнению с Вашим вариантом) в том что ничего не навязывается. Простой, житейский сюжет

- Жили-были обычные, нормальные структуры, все работало. Но в один прекрасный день возникла необходимость читать/писать в биты. Хорошо ли менять все обычные int и др на BitField<>? Очевидно нет, это "архитектурно неграмотно" т.к. меняем фундаментальные данные в угоду сиюминутной потребности. А если для структур написано достаточно много кода - такая замена уже нереальна.

Да, и поверьте, где разбить коленки об темплейты - всегда найдется :)

и "авто-слив", и печать, через boost.pfr тоже доступны.
Прошу показать (можно вкратце), интересно как

а заполнить theGlobalBitsMap без создания объекта не получится,
Может и можно, но лично я не знаю как. Есть какой-то мутный offsetof, но вроде он "не часть стандарта". Собсно нужно "идентифицировать поле"

для меня ваш код выглядит намного менее понятно, чем темплейты  :)
Что же такого непонятного я написал?  :)


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 08, 2021, 19:25
Цитировать
Не вижу где написано что адрес может уплыть
https://en.cppreference.com/w/cpp/language/typeid Notes:
Цитировать
There is no guarantee that the same std::type_info instance will be referred to by all evaluations of the typeid expression on the same type, although std::type_info::hash_code of those type_info objects would be identical, as would be their std::type_index.
Код
C++ (Qt)
const std::type_info& ti1 = typeid(A);
const std::type_info& ti2 = typeid(A);
 
assert(&ti1 == &ti2); // not guaranteed
assert(ti1.hash_code() == ti2.hash_code()); // guaranteed
assert(std::type_index(ti1) == std::type_index(ti2)); // guaranteed

Цитировать
Простой, житейский сюжет
но это не мой сюжет :) сырые битовые структуры и те, с которыми удобно работать — разные сущности.
Цитировать
Прошу показать (можно вкратце), интересно как
"авто-слив" в приведенном парсере уже по сути есть:
Код
C++ (Qt)
 boost::pfr::for_each_field(some_struct, [](auto const& f) {
   // делаем с полем f что хотим
   std::cout << f.size;
 });
можно и пользовательский вывод определить (не пробовал): https://www.boost.org/doc/libs/develop/doc/html/boost_pfr/tutorial.html#boost_pfr.tutorial.custom_printing_of_aggregates и следующий раздел

стандартный вывод просто печатает все поля по порядку:
Код
C++ (Qt)
std::cout << boost::pfr::io(some_struct);
Цитировать
Что же такого непонятного я написал?
все эти игры с typeid и указателями на члены, никогда не сталкивался :)


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 09, 2021, 09:52
https://en.cppreference.com/w/cpp/language/typeid Notes:
Да, верно, спасибо
"авто-слив" в приведенном парсере уже по сути есть:
Код
C++ (Qt)
 boost::pfr::for_each_field(some_struct, [](auto const& f) {
   // делаем с полем f что хотим
   std::cout << f.size;
 });
Ага, вот он, "момент истины". Оказывается дуст умеет "перебирать по полям" как я видел в статейке. А просто так заграбастать эту ф-цию нельзя? Пусть с мапой тоже несложно, но так еще лучше.

Хотя это совсем не означает что нужно искалечить все нормальные члены только для того чтобы иметь битовый размер. Кстати для статики почему бы не задействовать простецкие bitfield (от которых Вы гордо отказались). Напр
Код
C++ (Qt)
union CFlag {
 uint32 m_value;
 struct {
   uint32 m_A : 1;
   uint32 m_B : 2;
   ...
 };
};
И для мапы - только m_value. Это заметно схлопнет число всяких мелких полей. А на понты про union не обращайте внимания, то так, "что-то читал"  :)

но это не мой сюжет :) сырые битовые структуры и те, с которыми удобно работать — разные сущности.
Ну а зачем иметь/заводить 2 представления данных ? Я бы этого всячески избегал


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 09, 2021, 11:25
потому что на адрес нельзя закладываться
Да, посмотрел старый код, там у мапы есть еще функтор
Код
C++ (Qt)
bool СompareTypeInfo( const std::type_info * t0, const std::type_info * t1 )
{
 return t0->before(*t1);
}
Т.е. можно и так, хотя type_index явно удобнее


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 09, 2021, 23:35
Цитировать
Кстати для статики почему бы не задействовать простецкие bitfield (от которых Вы гордо отказались)
не очень понял как ваш пример мне поможет в случае, когда последнее поле не влезает в границу uint32 или uint64 (например, для uint32 индекс 30 и размер 5), или если структура больше 64 бит. как читать CFlag — тоже непонятно (fread? >>? проблемы с endianness опустим), плюс придется самому считать размеры таких полей чтоб все влезло (опять же, ненужная ручная работа). прошу продемонстрировать ©

если предполагается писать в m_value, а читать из битовых полей, то в С++ остается только верить, что компилятор сделает как ожидается, но давайте писать код, следующий стандарту :) Либо надо писать парсер на С, где такой трюк абсолютно законен.
Цитировать
И для мапы - только m_value
связь с мапой не уловил
Цитировать
Ну а зачем иметь/заводить 2 представления данных ? Я бы этого всячески избегал
с сырыми данными в моем случае крайне неудобно работать, их надо преобразовывать в более подходящие для «пользовательского» кода структуры.


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 10, 2021, 10:24
опять же, ненужная ручная работа
Может быть. Но гонять (большие) структуры сырые<->рабочие - мало не покажется, считаю оно того стоит.

не очень понял как ваш пример мне поможет в случае, когда последнее поле не влезает в границу uint32 или uint64 (например, для uint32 индекс 30 и размер 5), или если структура больше 64 бит. как читать CFlag — тоже непонятно (fread? >>? проблемы с endianness опустим), плюс придется самому считать размеры таких полей чтоб все влезло (опять же, ненужная ручная работа). прошу продемонстрировать ©

если предполагается писать в m_value, а читать из битовых полей, то в С++ остается только верить, что компилятор сделает как ожидается, но давайте писать код, следующий стандарту :) Либо надо писать парсер на С, где такой трюк абсолютно законен.
Чтение/запись получает адрес и число бит (не байт) которое берется из мапы. Стандартные операторы >> здесь применить не выйдет т.к. в одном байте могут сидеть части 2 структур.  Для bitfield я бы делал так

1) ограничил вместимость CFlag 32 битами. Если битовое поле >= инта, то почти наверняка компилятор как-то будет его равнять, и непрерывный битовый массив мы не получим, связываться с этим не стоит. Это нормально, делать большие члены битовыми неразумно.

2) Сделал бы последним членом подставу, напр m_padding, так чтобы сумма размеров = 32. Да, его размер придется вычислять руками, есть такая "шероховатость"  :)

Ну и считаем число бит
Код
C++ (Qt)
template<class CFlag>
int CalcFlagBits( void )
{
 static_assert(sizeof(CFlag) == sizeof(unit32));
 CFlag flag;
 flag.m_value = (uint32) -1;
 flag.m_padding = 0;
 return ffs(flag.m_value);   // по-моему эта ф-ция считает число единичек, могу путать
}

Интереснее как писать более продвинутые члены, напр контейнеры


Название: Re: чтение и запись битовых структур данных
Отправлено: Авварон от Май 10, 2021, 11:16
Может быть. Но гонять (большие) структуры сырые<->рабочие - мало не покажется, считаю оно того стоит.


Это называется "абстракция". Завтра вам надо другой формат поддерживать - удачи менять все структуры.
На самом деле такие "битовые" операции даже в моей текущей области (спутники) нужны (правда тут тупо кодом все читается, без декларативных структур) и вот у вас условно 10 похожих но разных форматов расположения битовых полей. Условно идет Хедер1 по которому мы понимаем идет ли дальше Хедер2А или Хедер2Б. В итоге всё это льется в огромный объект с кучей свойств - в зависимости от какие-то свойства есть, каких-то нет. Причем одни и те же свойства в разных вариантах на разной битовой позиции, ох я бы не хотел работать с битовыми структурами напрямую


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 10, 2021, 12:16
Это называется "абстракция". Завтра вам надо другой формат поддерживать - удачи менять все структуры.
Не понял что Вы хотели сказать :) Лично я считаю что данные должны существовать только в одном виде/экземпляре, а побитовый I/O - всего лишь один из вариантов (де)сериализации. Поэтому вариант с BitField<> (создание массивных структур чисто для I/O) осуждаю (ну осуждать много ума не надо  :))

Тут вот какая мысля. Для пачки битовых полей считаем размер автоматом. Так может и для др полей тоже? Чтобы избавиться от утомительной "регистрации" и заполнять мапу автоматом.


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 10, 2021, 13:32
Цитировать
Чтение/запись получает адрес и число бит (не байт) которое берется из мапы
то есть теперь эти оффсеты надо вычислять самому для заполнения мапы (да, делается один раз, но...), а не просто пользоваться руководством по требуемому битовому формату. а сколько будет веселья, если надо будет чуть-чуть их поменять, а они не влезут в вычисленные нами границы байт... :)
Цитировать
в одном байте могут сидеть части 2 структур
все для людей!
Цитировать
Если битовое поле >= инта, то почти наверняка компилятор как-то будет его равнять, и непрерывный битовый массив мы не получим, связываться с этим не стоит. Это нормально, делать большие члены битовыми неразумно.
нет, я решительно не хочу думать что там компилятор выравнивает и куда, я хочу решать свою прикладную задачу — просто напишу BitField<34>.
Цитировать
Сделал бы последним членом подставу, напр m_padding
еще и паддинг самому считать... :)
Цитировать
Ну и считаем число бит
вообще не понял что эта функция делает. при чем там -1 и число единиц?
Цитировать
Интереснее как писать более продвинутые члены, напр контейнеры
просто читаем элементы подряд. но тут темплейты, такой код слишком простой плохой :)
Код
C++ (Qt)
template<std::size_t Size, BitFieldSizeType Bits>
struct BitArray
{
 std::array<BitField<Bits>, Size> arr;
 
 auto parse(gsl::span<uint8_t> const& sp, uint64_t& offset)
 {
   for (auto& el : arr)
     el.parse(sp, offset);
   return arr;
 }
};
конечно, каждому контейнеру надо будет писать свою реализацию. но тут можно блеснуть словом «концепт», сказав, что мы поддерживаем любую структуру данных, которая имеет метод auto parse(gsl::span<uint8_t> const& sp, uint64_t& offset). (кстати, на эту же тему недавно нашел нечто похожее на протоколы из objc/swift: https://github.com/ldionne/dyno)


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 10, 2021, 14:36
то есть теперь эти оффсеты надо вычислять самому для заполнения мапы (да, делается один раз, но...), а не просто пользоваться руководством по требуемому битовому формату.
Такое "руководство" по-любому будет, только я предлагаю написать его как создание мапы, а не создавать новые типы, а потом еще перегонять данные.

а сколько будет веселья, если надо будет чуть-чуть их поменять, а они не влезут в вычисленные нами границы байт... :)
Да, придется  немного подсуетиться. Но гораздо вероятнее вариант "изменилось но еще влезает" - и мне ничего делать не надо, а Вам...

еще и паддинг самому считать... :)
Никто не заставляет, можно тупенько почленно, просто заманчиво резко сократить число строк "руководства".

просто напишу BitField<34>.
Да, отказаться от уже написанного кода непросто  :)


Название: Re: чтение и запись битовых структур данных
Отправлено: kambala от Май 10, 2021, 15:47
у меня нет никакого «написанного» кода, есть пока лишь пара тестовых примеров. но ваш подход кажется чрезмерно переусложненным по сравнению с декларативным темплейтным. демонстрации аналога http://www.prog.org.ru/index.php?topic=33021.msg244833#msg244833 также не последовало.

дальнейший спор считаю бессмысленным, победила дружба :)


Название: Re: чтение и запись битовых структур данных
Отправлено: Igors от Май 11, 2021, 07:21
у меня нет никакого «написанного» кода, есть пока лишь пара тестовых примеров. но ваш подход кажется чрезмерно переусложненным по сравнению ..
Возможно Вы "по инерции" считаете что есть "сырые" и "рабочие" данные, но я предлагаю иметь только рабочие. Сравним 2 варианта
Код:
struct BasicBitItem
{
  BitField<16> header;
...
Код:
BasicBitItem item;

  SetBitsCount(item, item.header, 16);
...
"Расходы по написанию" одинаковы. Контроль ошибок тот же. Если нужен "битовый размер поля" - то почему его не создать самым простым способом (мапой)? Зачем городить немалые структуры только для I/O, а потом еще перегружать данные туда-сюда? 

Правда возможно что "битовый размер" - еще не все что нужно I/O. Вполне вероятно что читаются/пишутся поля которых нет в рабочих данных, и наоборот. И в моем варианте неясно как уложить такие в "руководство". Ну проблема их заполнения все равно остается. Но это уже чисто мои догадки  :)

дальнейший спор считаю бессмысленным, победила дружба :)
Согласен  :)