Как извлечь значения из структуры C++ с помощью битовых полей

Это моя проблема, у меня есть структура (которую я не могу изменить), например:

struct X
{
    uint8_t fieldAB;
    uint8_t fieldCDE;
    uint8_t fieldFGH;
    ...
}

Каждое поле этой структуры содержит разные значения, упакованные с использованием битовой маски (битового поля), то есть, например, fieldAB содержит два разных значения (A и B) в полубайтах hi/lo, а fieldCDE содержит три разных значения (C, D и E с следующая битовая маска: бит 7-6, бит 5-4-3, бит 2-1-0) и так далее...

Я хотел бы написать простой API для чтения и записи этого значения с помощью enum, который позволяет легко получить доступ к значениям каждого поля:

getValue(valueTypeEnum typeOfValue, X & data);
setValue(valueTypeEnum typeOfValue, X & data, uint8_t value);

Где перечисление valueTypeEnum выглядит примерно так:

enum valueTypeEnum
{
    A,
    B,
    C,
    D,
    E,
    ...
}

Моя идея состояла в том, чтобы использовать карту (словарь), которая с учетом valueTypeEnum возвращает используемую битовую маску и смещение для доступа к правому полю структуры, но я думаю, что это немного сложно и не так элегантно. Каковы ваши предложения?


person Kill KRT    schedule 16.07.2014    source источник
comment
Как насчет битовых полей?   -  person Quentin    schedule 17.07.2014
comment
Я забыл упомянуть, что я не могу вносить какие-либо изменения в структуру. Я обновил исходный пост.   -  person Kill KRT    schedule 17.07.2014
comment
Почему здесь тег C++? Если вы использовали C++, используйте std::bitset для управления битами.   -  person tillaert    schedule 17.07.2014


Ответы (4)


Я могу придумать несколько способов, как это можно сделать, самый простой — использовать битовые поля непосредственно в вашей структуре:

struct X {
   uint32_t A : 4; // 4 bits for A. 
   uint32_t B : 4; 
   uint32_t C : 4; 
   uint32_t D : 4; 
   uint8_t E : 7; 
   uint8_t F : 1;
};

Затем вы можете легко получить или установить значения, используя, например:

X x;
x.A = 0xF;

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

Как указано в комментариях, фактическое поведение битовых полей может зависеть от вашей платформы, поэтому, если пространство имеет существенное значение, вы должны убедиться, что оно ведет себя так, как вы ожидаете. Также см. здесь дополнительную информацию о битовых полях в C++.

person Stian Svedenborg    schedule 16.07.2014
comment
Проголосовали против, потому что использование любого другого типа, кроме «int», определенно не поддерживалось для битового поля в C, и OP не указал, какие флаги компилятора C или стандартов используются. Следствием этого является то, что эта опция может работать или не работать в зависимости от того, как и для какого процессора компилируется код. - person rivimey; 17.07.2014
comment
FWIW, я бы хотел, чтобы версия Стиана работала так, как вы ожидаете, и, возможно, в конкретном случае OP так и есть. Однако интерпретация C99 полей E и F может заключаться в том, что они представляют собой не последовательные байты, а последовательные длинные слова. - person rivimey; 17.07.2014
comment
Я ответил с точки зрения C ++, так как именно так был помечен OP. Насколько мне известно, большинство компиляторов C++ допускают использование байтов в наборах битов. Я бы сказал, что предупреждение о том, что на некоторых платформах это может работать не так, как ожидается, является лучшим комментарием, чем не делайте этого. - person Stian Svedenborg; 17.07.2014
comment
Эта страница: en.cppreference.com/w/cpp/language/bit_field говорит, что упаковка полей определяется реализацией, что означает, что компилятор может выбирать, что ему проще всего сделать. Я согласен, однако, что С++ разрешает контейнеры байтов. В результате я отменю отрицательный голос. - person rivimey; 17.07.2014

Я еще немного покопаюсь в битовых полях:

Ваша структура X остается неизменной:

struct X
{
    uint8_t fieldAB;
    uint8_t fieldCDE;
    uint8_t fieldFGH;
};

Давайте определим союз для легкого перевода:

union Xunion {
    X x;
    struct Fields { // Named in case you need to sizeof() it
        uint8_t A   : 4;
        uint8_t B   : 4;

        uint8_t C   : 2;
        uint8_t D   : 3;
        uint8_t E   : 3;

        uint8_t F   : 2;
        uint8_t G   : 3;
        uint8_t H   : 3;
    };
};

И теперь вы можете легко получить доступ к этим битовым полям.

Прежде чем кто-нибудь попытается содрать с меня кожу, обратите внимание, что это никоим образом не является переносимым и даже не определено стандартом C++. Но он будет делать то, что вы ожидаете от любого здравомыслящего компилятора.

Вы можете добавить специфичную для компилятора директиву упаковки (например, __attribute__((packed)) GCC) в структуру Fields, а также static_assert, гарантирующую, что sizeof оба члена объединения строго равны.

person Quentin    schedule 17.07.2014
comment
определенно есть статическое утверждение. По крайней мере, вас предупредят, если что-то пойдет не так. - person rivimey; 17.07.2014

Лучше всего, IMO, вообще забыть об использовании структуры или, если это так, использовать объединение структуры и массива байтов. Затем ваши функции доступа используют массив байтов, для которого легко вычислить смещения и так далее. Делая это, я считаю, что вы гарантированно получите доступ к нужным вам байтам.

Недостатком является то, что вам придется повторно собирать 16- и 32-битные значения, если таковые имеются, найденные в структуре, и делать это с учетом любых проблем с порядком байтов. Если вы знаете, что любые такие значения находятся на границах 16- или 32-битных адресов, вы можете использовать объединенные короткие и длинные массивы, чтобы сделать это, что, вероятно, было бы лучше, хотя и несколько непрозрачно.

ХТН

person rivimey    schedule 16.07.2014

Может быть, я нашел решение. Я могу создать API следующим образом:

uint8_t getValue(valueTypeEnum typeOfValue, X * data)
{
    uint8_t bitmask;
    uint8_t * field = getBitmaskAndFieldPtr(typeOfValue, data, &bitmask);

    return ((*field) & bitmask) >> ...;
}

void setValue(valueTypeEnum typeOfValue, X * data, uint8_t value)
{
    uint8_t bitmask;
    uint8_t * field = getBitmaskAndFieldPtr(typeOfValue, data, &bitmask);

    *field = ((*field) & !bitmask) | (value << ...);
}

uint8_t * getBitmaskAndFieldPtr(valueTypeEnum typeOfValue, X * data, uint8_t * bitmask)
{
    uint8_t * fieldPtr = 0;

    switch(typeOfValue)
    {
         case A:
         {
               *bitmask = 0x0F; // I can use also a dictionary here
               fieldPtr = &data.AB;
               break;
         }
         case B:
         {
               *bitmask = 0xF0; // I can use also a dictionary here
               fieldPtr = &data.AB;
               break;
         }
         case C:
         {
               *bitmask = 0xC0; // I can use also a dictionary here
               fieldPtr = &data.CDE;
               break;
         }
         ...    
    }

    return fieldPtr;
}

Я знаю, что switch-case не так элегантен и требует обновления каждый раз при изменении структуры, но я не вижу автоматического способа (возможно, с использованием отражения) решить эту проблему.

person Kill KRT    schedule 17.07.2014
comment
Если вы не можете использовать решение объединения от Квентина, у меня возникло бы искушение написать группу пар функций, чтобы получить и установить каждый элемент независимо, пометить их как «встроенные» и вставить в файл заголовка. Я бы не стал пытаться быть универсальным в отношении вызовов (например, valueTypeEnum). - person rivimey; 17.07.2014