Как получить массив битов в структуре?

Я размышлял (и поэтому ищу способ узнать это, а не лучшее решение), можно ли получить массив битов в структуре.

Позвольте мне продемонстрировать на примере. Представьте себе такой код:

#include <stdio.h>

struct A
{
    unsigned int bit0:1;
    unsigned int bit1:1;
    unsigned int bit2:1;
    unsigned int bit3:1;
};

int main()
{
    struct A a = {1, 0, 1, 1};
    printf("%u\n", a.bit0);
    printf("%u\n", a.bit1);
    printf("%u\n", a.bit2);
    printf("%u\n", a.bit3);
    return 0;
}

В этом коде у нас есть 4 отдельных бита, упакованных в структуру. К ним можно обращаться по отдельности, оставляя компилятору работу по манипуляциям с битами. Мне было интересно, возможно ли такое:

#include <stdio.h>

typedef unsigned int bit:1;

struct B
{
    bit bits[4];
};

int main()
{
    struct B b = {{1, 0, 1, 1}};
    for (i = 0; i < 4; ++i)
        printf("%u\n", b.bits[i]);
    return 0;
}

Я пытался объявить bits в struct B как unsigned int bits[4]:1 или unsigned int bits:1[4] или подобные вещи, но безрезультатно. Мое лучшее предположение состояло в том, чтобы typedef unsigned int bit:1; и использовать bit в качестве типа, но все равно не работает.

У меня вопрос, возможно ли такое? Если да, то как? Если нет, то почему? 1-битное целое число без знака является допустимым типом, так почему бы вам не получить его массив?

Опять же, я не хочу замены этому, мне просто интересно, как такое возможно.

P.S. Я помечаю это как C++, хотя код написан на C, потому что я предполагаю, что этот метод будет существовать на обоих языках. Если есть особый способ сделать это на С++ (с использованием языковых конструкций, а не библиотек), мне также было бы интересно узнать.

ОБНОВЛЕНИЕ: я полностью осознаю, что могу сам выполнять битовые операции. Я делал это тысячу раз в прошлом. Меня НЕ интересует ответ, в котором говорится, что вместо этого используйте массив/вектор и выполняйте битовые манипуляции. Я думаю только о том, возможна ли ЭТА КОНСТРУКЦИЯ или нет, а НЕ альтернатива.

Обновление: ответ для нетерпеливых (спасибо neagoegab):

Вместо

typedef unsigned int bit:1;

я мог бы использовать

typedef struct
{
    unsigned int value:1;
} bit;

правильно используя #pragma pack


person Shahbaz    schedule 15.11.2011    source источник
comment
Использование битовых полей затруднит портирование вашей программы... вы уверены, что они вам нужны?   -  person Carl Norum    schedule 16.11.2011
comment
@CarlNorum Разве я не упоминал 10 раз, что мне просто любопытно?   -  person Shahbaz    schedule 16.11.2011
comment
В таком случае, насколько мне известно, нет возможности создать массив битовых полей.   -  person Carl Norum    schedule 16.11.2011
comment
@CarlNorum, кроме того, разве не должно быть наоборот? Я имею в виду, что, используя битовые поля, вы избегаете выполнения побитовых операций, что означает, что вы избегаете делать что-то, что зависит от большого/малого порядка байтов или дополнения 1/2. Компилятор обрабатывает побитовую операцию и будет делать это правильно для каждой архитектуры. Разве это не так?   -  person Shahbaz    schedule 16.11.2011
comment
Расположение битовых полей зависит от реализации. Так что нет, вместо того, чтобы быть проще, он часто оказывается кошмаром портативности. Все побитовые операторы имеют четко определенную семантику.   -  person Carl Norum    schedule 16.11.2011
comment
@CarlNorum: Это просто кошмар переносимости, если вы пытаетесь преобразовать свою структуру в массив байтов или из него и ожидаете определенного макета. Если все, что вы хотите сделать, это эффективно хранить биты в структуре, тогда нет проблем с переносимостью.   -  person Craig McQueen    schedule 16.11.2011


Ответы (6)


НЕ ВОЗМОЖНО – A такая конструкция НЕВОЗМОЖНА(здесь) – НЕ ВОЗМОЖНО

Можно попытаться сделать это, но результатом будет один бит хранится в одном байте

#include <cstdint>
#include <iostream>
using namespace std;

#pragma pack(push, 1)
struct Bit
{
    //one bit is stored in one BYTE
    uint8_t a_:1;
};
#pragma pack(pop, 1)
typedef Bit bit;

struct B
{
    bit bits[4];
};

int main()
{
    struct B b = {{0, 0, 1, 1}};
    for (int i = 0; i < 4; ++i)
        cout << b.bits[i] <<endl;

    cout<< sizeof(Bit) << endl;
    cout<< sizeof(B) << endl;

    return 0;
}

выход:

0 //bit[0] value
0 //bit[1] value
1 //bit[2] value
1 //bit[3] value
1 //sizeof(Bit), **one bit is stored in one byte!!!**
4 //sizeof(B), ** 4 bytes, each bit is stored in one BYTE**

Чтобы получить доступ к отдельным битам из байта, вот пример (Обратите внимание, что расположение битовых полей зависит от реализации)

#include <iostream>
#include <cstdint>
using namespace std;

#pragma pack(push, 1)
struct Byte
{
    Byte(uint8_t value):
        _value(value)
    {
    }
    union
    {
    uint8_t _value;
    struct {
        uint8_t _bit0:1;
        uint8_t _bit1:1;
        uint8_t _bit2:1;
        uint8_t _bit3:1;
        uint8_t _bit4:1;
        uint8_t _bit5:1;
        uint8_t _bit6:1;
        uint8_t _bit7:1;
        };
    };
};
#pragma pack(pop, 1)

int main()
{
    Byte myByte(8);
    cout << "Bit 0: " << (int)myByte._bit0 <<endl;
    cout << "Bit 1: " << (int)myByte._bit1 <<endl;
    cout << "Bit 2: " << (int)myByte._bit2 <<endl;
    cout << "Bit 3: " << (int)myByte._bit3 <<endl;
    cout << "Bit 4: " << (int)myByte._bit4 <<endl;
    cout << "Bit 5: " << (int)myByte._bit5 <<endl;
    cout << "Bit 6: " << (int)myByte._bit6 <<endl;
    cout << "Bit 7: " << (int)myByte._bit7 <<endl;

    if(myByte._bit3)
    {
        cout << "Bit 3 is on" << endl;
    }
}
person neagoegab    schedule 15.11.2011
comment
Что касается вашего второго метода, разве этот союз не получает все биты _bit* в одном и том же перекрывающемся бите? Вы имели в виду struct Bit{ union{ char _value; struct { char _bit0:1; char _bit1:1; ....} _bits; }; };? - person Shahbaz; 16.11.2011
comment
Не могли бы вы объяснить эти pragma pack? Являются ли они стандартными C или специфичными для Visual C? - person Shahbaz; 16.11.2011
comment
pragma pack определяется реализацией, поскольку это не pragma STDC (ISO 9899:1999(S)6.10.6.1). Насколько я знаю, он определен для MSVC и GCC. - person moshbear; 16.11.2011
comment
Посмотрите en.wikipedia.org/wiki/Data_structure_alignment, чтобы получить более подробное объяснение того, что именно pragma pack делает. - person moshbear; 16.11.2011
comment
@Shahbaz, да, так и должно быть, извините за позднее обновление и спасибо. - person neagoegab; 24.12.2013
comment
Оглядываясь назад на этот ответ, я не уверен, почему я его принял. Ваш массив битов на самом деле не массив битов. Если sizeof(B) равно 4 (в первом методе), то вам действительно удалось сделать не массив битов, а массив байтов! - person Shahbaz; 19.03.2014
comment
@Shahbaz, как вы можете видеть в ответе, в первой строке четко указано, что такая конструкция невозможна. Я думал, вы поняли, что бит хранится в одном байте, и для этого используется пакет прагмы. Массив любого типа представляет собой массив битов... - person neagoegab; 19.03.2014

В С++ вы используете std::bitset<4>. Это позволит использовать минимальное количество слов для хранения и скроет от вас всю маскировку. Очень сложно отделить библиотеку C++ от языка, потому что большая часть языка реализована в стандартной библиотеке. В C нет прямого способа создать такой массив из отдельных битов, вместо этого вы должны создать один элемент из четырех битов или выполнить манипуляции вручную.

РЕДАКТИРОВАТЬ:

1-битное целое число без знака является допустимым типом, так почему бы вам не получить его массив?

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

person Mark B    schedule 15.11.2011
comment
Хотя я прямо упомянул, что меня не интересуют библиотеки (поскольку реализовать их самостоятельно было бы в любом случае просто), но тем не менее +1, потому что я узнал, что std::bitset существует. - person Shahbaz; 16.11.2011

С++ будет использовать std::vector<bool> или std::bitset<N>.

В C для эмуляции семантики std::vector<bool> вы используете такую ​​структуру:

struct Bits {
    Word word[];
    size_t word_count;
};

где Word — определяемый реализацией тип, равный по ширине шине данных ЦП; wordsize, как будет использоваться позже, равно ширине шины данных.

Например. Word это uint32_fast_t для 32-битных машин, uint64_fast_t для 64-битных машин; wordsize равно 32 для 32-битных машин и 64 для 64-битных машин.

Вы используете функции/макросы для установки/очистки битов.

Чтобы извлечь немного, используйте GET_BIT(bits, bit) (((bits)->)word[(bit)/wordsize] & (1 << ((bit) % wordsize))).

Чтобы установить бит, используйте SET_BIT(bits, bit) (((bits)->)word[(bit)/wordsize] |= (1 << ((bit) % wordsize))).

Чтобы немного очистить, используйте CLEAR_BIT(bits, bit) (((bits)->)word[(bit)/wordsize] &= ~(1 << ((bit) % wordsize))).

Чтобы немного перевернуть, используйте FLIP_BIT(bits, bit) (((bits)->)word[(bit)/wordsize] ^= (1 << ((bit) % wordsize))).

Чтобы добавить изменение размера в соответствии с std::vector<bool>, создайте функцию изменения размера, которая вызывает realloc для Bits.word и соответствующим образом изменяет Bits.word_count. Точные детали этого остаются проблемой.

То же самое относится к правильной проверке диапазона битовых индексов.

person moshbear    schedule 15.11.2011
comment
Спасибо, что потрудились объяснить битовые операции, но для меня это было очевидно. Мой вопрос был об этом конкретном использовании (с битами, управляемыми компилятором, а не мной). Опять же, я на самом деле не использую такую ​​​​вещь, а просто размышляю об этом. - person Shahbaz; 16.11.2011
comment
Цитирование руководства по стилю кодирования JSF: следует избегать структур битовых наборов, если только они не взаимодействуют с оборудованием. Для этого есть веская причина - они доставляют больше хлопот, чем пользы. Я почти уверен, что это тоже есть в MISRA, но у меня нет лишних 15 долларов на покупку бумаги, поэтому я не могу дать окончательный ответ. - person moshbear; 16.11.2011
comment
@Shahbaz: Нет, компилятор не может этого сделать. - person Mooing Duck; 16.11.2011
comment
Даже при взаимодействии с аппаратным обеспечением нативные битовые поля малопригодны, конечно, не в том смысле, что они переносимы и не требуют от пользователя тщательной проверки того, как работает их реализация, прежде чем писать какой-либо код. Я сделал свои собственные библиотеки битовых полей/порядков байтов, в том числе ту, которая позволяет использовать «массивы битовых полей», чтобы исправить это, и никогда не оглядывался назад. - person underscore_d; 20.01.2016

это оскорбительно и зависит от расширения... но у меня это сработало:

struct __attribute__ ((__packed__)) A
{
    unsigned int bit0:1;
    unsigned int bit1:1;
    unsigned int bit2:1;
    unsigned int bit3:1;
};
union U
{
    struct A structVal;
    int intVal;
};

int main()
{
    struct A a = {1, 0, 1, 1};
    union U u;
    u.structVal = a;
    for (int i =0 ; i<4; i++)
    {
        int mask = 1 << i;
        printf("%d\n", (u.intVal &  mask) >> i);
    }
    return 0;
}
person Grady Player    schedule 04.05.2015

Вы также можете использовать массив целых чисел (целых или длинных) для построения произвольно большой битовой маски. Системный вызов select() использует этот подход для своего типа fd_set; каждый бит соответствует пронумерованному файловому дескриптору (0..N). Определены макросы: FD_CLR для очистки бита, FD_SET для установки бита, FD_ISSET для проверки бита и FD_SETSIZE для общего количества битов. Макросы автоматически определяют, к какому целому числу в массиве обращаться и к какому биту в целом числе. В Unix см. "sys/select.h"; под Windows, я думаю, он находится в "winsock.h". Вы можете использовать метод FD для создания собственных определений битовой маски. Я полагаю, что в C++ вы могли бы создать объект битовой маски и перегрузить оператор [] для доступа к отдельным битам.

person Alex Measday    schedule 15.11.2011

Вы можете создать битовый список, используя указатель структуры. Однако это будет использовать больше, чем бит пространства на бит, поскольку он будет использовать один байт (для адреса) на бит:

struct bitfield{
    unsigned int bit : 1;
};
struct bitfield *bitstream;

Затем после этого:

bitstream=malloc( sizeof(struct bitfield) * numberofbitswewant );

Вы можете получить к ним доступ следующим образом:

bitstream[bitpointer].bit=...
person Dominic Mason    schedule 19.01.2016
comment
Вы действительно пробовали это? Прежде всего, sizeof(struct bitfield) сам по себе округляется до размера int (я пробовал это с gcc), так что даже если это сработает, ваш malloc будет выделять слишком много памяти. Вместо этого вам нужно что-то вроде malloc(number_of_bits / CHAR_BIT);. Но, тем не менее, заполнение (опять же, проверенное с помощью gcc) делает каждый элемент своим собственным unsigned int, поэтому ваш bitstream[bitpointer].bit будет иметь тот же адрес, если вы удалите : 1 из объявления bit, поэтому вы на самом деле не получите битовое поле. - person Shahbaz; 20.01.2016
comment
Я попробовал ваш код следующим образом: for (int i = 0; i < number_of_bits; ++i) bitstream[i].bit = i % 2; и позже распечатал биты, глядя на фактический битовый шаблон памяти: printf("%08X %08X %08X %08X\n", ((uint32_t *)bf)[0], ((uint32_t *)bf)[1], ((uint32_t *)bf)[2], ((uint32_t *)bf)[3]); Результат был 00000000 00000001 00000000 00000001 - person Shahbaz; 20.01.2016
comment
Благодарю вас! Я пробовал это, и я должен был упомянуть, что это бесполезно для экономии места, поскольку размер указателя на структуру будет следующим доступным адресом. Однако это полезно, если вы ищете эффект зацикливания (1 + 1 снова становится нулем). Я отредактирую свой пост, чтобы упомянуть проблему с памятью. - person Dominic Mason; 20.01.2016
comment
Если это не сохраняет память, в чем смысл? В этом случае мы могли бы просто использовать bool[]. - person underscore_d; 20.01.2016