Записи структуры множества битовые поля. Битовые поля

В языке С++ есть возможность задавать элементам структур определённое количество памяти в битах. Например, если необходимо создать структуру данных, соответствующую размеру регистра в каком-либо устройстве. Типом элемента (его называют битовым полем) такой структуры может быть целочисленное (чаще всего типа unsigned ) или перечислимое (enum ) .

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

Битовые поля в С++

struct fieldbite { unsigned short first: 2; unsigned short second: 2; unsigned short third: 4; } field;

struct fieldbite

unsigned short first : 2 ;

unsigned short second : 2 ;

unsigned short third : 4 ;

} field ;

Мы определили структуру, в которой переменные будут занимать указанное количество бит. 2 + 2 + 4 дает 8 бит (мы выровняли до размера байта). Если в эту структуру дописать еще unsigned short fifth : 5 ; – уже будет задействовано 2 байта. Во втором байте естественно будет мусор 8 – 5 = 3 бита, которые будут невостребованными.

В отличии от объединений (union) размер битовых полей варьируется, в зависимости от того, сколько бит программист заказал. Если заказано 7 бит (скажем две переменные по 3 бита, и одна – 1 бит), то С++ отведет один байт (8 бит) под эти три переменные.

Если программист закажет 11 бит, то С++ отведет два байта (16 бит). Причем во втором байте будут задействованы только 5 бит, а остальные скорее всего будут, как бесполезный хвост. Поэтому при описании битовых полей следует учитывать такое “выравнивание” до байта. Т.е. распределять в нем переменные так, чтоб каждый бит был востребован. Для выравнивания занимаемой памяти можно использовать неименованные битовые поля.

Приведем еще один короткий пример, в котором битовые поля отводятся под дату и время для демонстрации этой технологии.

Битовые поля в C++

#include using namespace std; struct DateTime //Опишем структуру с битовыми полями { unsigned short Day: 5; //5 бит для дня unsigned short Month: 4; //4 для месяца unsigned short Year: 7; //7 для года от 0 до 99 unsigned short Hour: 5; //5 бит для 24-х часов unsigned short Minute: 6;//6 для минут unsigned short Second: 6;//6 для секунд }; int main() { DateTime d; //объявляем переменную этого типа с битовыми полями int i; //И еще одну, в которую будет поступать ввод данных //Введем дату cout << "Input day (1-31):" << "\t"; cin >> i; d.Day = i; cout << "Input month (1-12):" << "\t"; cin >> i; d.Month = i; cout << "Input Year (00-99) :" << "\t"; cin >> i; d.Year = i; //Введем время cout << endl << "Input Hour (0-24):" << "\t"; cin >> i; d.Hour = i; cout << "Input Minute (0-60):" << "\t"; cin >> i; d.Minute = i; cout << "Input Seconds (0-60):" << "\t"; cin >> i; d.Second = i; //И выведем их с показателем размера в памяти cout << endl << "Date is: " << d.Day << "." << d.Month << ".20" << d.Year << " "; cout << d.Hour << ":" << d.Minute << ":" << d.Second << endl; cout << sizeof(d) << endl; }

#include

using namespace std ;

struct DateTime //Опишем структуру с битовыми полями

unsigned short Day : 5 ; //5 бит для дня

unsigned short Month : 4 ; //4 для месяца

unsigned short Year : 7 ; //7 для года от 0 до 99

unsigned short Hour : 5 ; //5 бит для 24-х часов

unsigned short Minute : 6 ; //6 для минут

unsigned short Second : 6 ; //6 для секунд

int main ()

DateTime d ; //объявляем переменную этого типа с битовыми полями

int i ; //И еще одну, в которую будет поступать ввод данных

//Введем дату

cout << "Input day (1-31):" << "\t" ; cin >> i ; d . Day = i ;

cout << "Input month (1-12):" << "\t" ; cin >> i ; d . Month = i ;

cout << "Input Year (00-99) :" << "\t" ; cin >> i ; d . Year = i ;

//Введем время

cout << endl << "Input Hour (0-24):" << "\t" ; cin >> i ; d . Hour = i ;

cout << "Input Minute (0-60):" << "\t" ; cin >> i ; d . Minute = i ;

cout << "Input Seconds (0-60):" << "\t" ; cin >> i ; d . Second = i ;

В противоположность другим компьютерным языкам С имеет возможность, называемую битовыми полями, позволяющую работать с отдельными битами. Битовые поля полезны по нескольким причинам. Ниже приведены три из них:

  1. Если ограничено место для хранения информации, можно сохранить несколько логических (истина/ложь) переменных в одном байте.
  2. Некоторые интерфейсы устройств передают информацию, закодировав биты в один байт.
  3. Некоторым процедурам кодирования необходимо получить доступ к отдельным битам в байте.

Хотя все эти функции могут выполняться с помощью битовых операторов, битовые поля могут внести большую ясность в программу.

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

struct имя структуры {
тип имя1: длина;
тип имя2: длина;
...
тип имяN: длина;
}

Битовые поля должны объявляться как int, unsigned или signed. Битовые поля длиной 1 должны объявляться как unsigned, поскольку 1 бит не может иметь знака. Битовые поля могут иметь длину от 1 до16 бит для 16-битных сред и от 1 до 32 бит для 32-битных сред. В Borland С++ самый левый бит является знаковым.

Рассмотрим приведенное ниже определение структуры:

Struct device {
unsigned active: 1;
unsigned ready: 1;
unsigned xmt_error: 1;
} dev_code;

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

Void wr_tape(char с)
{
while(!dev_code.ready) rd(&dev_code); /* ждать */
wr_to__tape (с); /* запись байта */
while(dev_code.active) rd(&dev_code); /* ожидание окончания записи информации */
if(dev_code.xmt error) printf("Write Error");
}

Здесь rd() возвращает статус ленточного накопителя wr_to_tape(), записывает данные. Рисунок показывает, как выглядит переменная dev_code в памяти.

Как можно видеть из предыдущего примера, к каждому полю происходит обращение с помощью оператора "точка". Тем не менее если обращение к структуре происходит с помощью указателя, то следует использовать оператор ->.

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

Struct device {
unsigned active: 1;
unsigned ready: 1;
unsigned xmt_error: 1;
unsigned: 2;
unsigned EOT: 1;
} dev_code;

Битовые поля имеют некоторые ограничения. Нельзя получить адрес переменной битового поля. Переменные битового поля не могут помещаться в массив. Переходя с компьютера на компьютер нельзя быть уверенным в порядке изменения битов (слева направо или справа налево). Любой код, использующий битовые поля, зависит от компьютера.

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

Struct emp {
struct addr address;
float pay;
unsigned lay_off:1;
unsigned hourly:1;
unsigned deductions:3;
};

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

Недавно познакомился со структурами C/C++ - struct. Господи, да «что же с ними знакомиться» скажете вы? Тем самым вы допустите сразу 2 ошибки: во-первых я не Господи, а во вторых я тоже думал что структуры - они и в Африке структуры. А вот как оказалось и - нет. Я расскажу о нескольких жизненно-важных подробностях, которые кого-нибудь из читателей избавят от часовой отладки…

Выравнивание полей в памяти

Обратите внимание на структуру:

Struct Foo { char ch; int value; };
Ну во-первых какой у этой структуры размер в памяти? sizeof(Foo) ?
Размер этой структуры в памяти зависит от настроек компилятора и от директив в вашем коде…

В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые - выравниваются на чётные позиции, 4-байтовые - на позиции кратные четырём и т.д. В большинстве случаев (или просто предположим что сегодня это так) выравнивание размера структуры в памяти составляет 4 байта. Таким образом, sizeof(Foo) == 8 . Где и как прилепятся лишние 3 байта? Если вы не знаете - ни за что не угадаете…

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: пусто
  • 4 байт: пусто
  • 5 байт: value
  • 6 байт: value
  • 7 байт: value
  • 8 байт: value
Посмотрим теперь размещение в памяти следующей структуры:

Struct Foo { char ch; short id; int value; };
Оно выглядит вот так:

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: id
  • 4 байт: id
  • 5 байт: value
  • 6 байт: value
  • 7 байт: value
  • 8 байт: value
То есть, то что можно впихнуть до выравнивания по 4 байта - впихивается на ура (без увеличения размера структуры в памяти), добавим ещё одно поле:

Struct Foo { char ch; short id; short opt; int value; };
Посмотрим на размещение полей в памяти:

  • 1 байт: ch
  • 2 байт: пусто
  • 3 байт: id
  • 4 байт: id
  • 5 байт: opt
  • 6 байт: opt
  • 7 байт: пусто
  • 8 байт: пусто
  • 9 байт: value
  • 10 байт: value
  • 11 байт: value
  • 12 байт: value
Всё это ой как печально, но есть способ бороться с этим прямо из кода:

#pragma pack(push, 1) struct Foo { // ... }; #pragma pack(pop)
Мы установили размер выравнивания в 1 байт, описали структуру и вернули предыдущую настройку. Возвращать предыдущую настройку - категорически рекомендую. Иначе всё может закончиться очень плачевно. У меня один раз такое было - падало Qt. Где-то заинклюдил их.h-ник ниже своего.h-ника…

Битовые поля

В комментариях мне указали на то, что битовые поля в структурах по стандарту являются «implementation defined» - потому их использования лучше избежать, но для меня соблазн слишком велик...

Мне становится не то что неспокойно на душе, а вообще становится хреново, когда я вижу в коде заполнение битовых полей при помощи масок и сдвигов, например так:

Unsigned field = 0x00530000; // ... field &= 0xFFFF00FF; field |= (id) << 8; // ... field &= 0xFFFFFF83; field |= (proto) << 2;
Всё это пахнет такой печалью и такими ошибками и их отладкой, что у меня сразу же начинается мигрень! И тут из-за кулис выходят они - Битовые Поля. Что самое удивительное - были они ещё в языке C, но кого ни спрашиваю - все в первый раз о них слышат. Этот беспредел надо исправлять. Теперь буду давать им всем ссылку, ну или хотя бы ссылку на эту статью.

Как вам такой кусок кода:

#pragma pack(push,1) struct IpHeader { uint8_t header_length:4; uint8_t version:4; uint8_t type_of_service; uint16_t total_length; uint16_t identificator; // Flags uint8_t _reserved:1; uint8_t dont_fragment:1; uint8_t more_fragments:1; uint8_t fragment_offset_part1:5; uint8_t fragment_offset_part2; uint8_t time_to_live; uint8_t protocol; uint16_t checksum; // ... }; #pragma pack(pop)
А дальше в коде мы можем работать с полями как и всегда работаем с полями в C/C++. Всю работу по сдвигам и т.д. берет на себя компилятор. Конечно же есть некоторые ограничения… Когда вы перечисляете несколько битовых полей подряд, относящихся к одному физическому полю (я имею ввиду тип который стоит слева от имени битового поля) - указывайте имена для всех битов до конца поля, иначе доступа к этим битам у вас не будет, иными словами кодом:

#pragma pack(push,1) stuct MyBitStruct { uint16_t a:4; uint16_t b:4; uint16_t c; }; #pragma pack(pop)
Получилась структура на 4 байта! Две половины первого байта - это поля a и b . Второй байт не доступен по имени и последние 2 байта доступны по имени c . Это очень опасный момент. После того как описали структуру с битовыми полями обязательно проверьте её sizeof !

Также порядок размещения битовых болей в байте зависит от порядка байтов. При порядке LITTLE_ENDIAN битовые поля раздаются начиная со первых байтов, при BIG_ENDIAN - наоборот…

Порядок байтов

Меня также печалят в коде вызовы функций htons() , ntohs() , htonl() , nthol() в коде на C++. На C это ещё допустимо, но не на С++. С этим я никогда не смирюсь! Внимание всё нижесказанное относится к C++!

Ну тут я буду краток. Я в одной из своих предыдущих статей уже писал что нужно делать с порядками байтов. Есть возможность описать структуры, которые внешне работают как числа, а внутри сами определяют порядок хранения в байтах. Таким образом наша структура IP-заголовка будет выглядеть так:

#pragma pack(push,1) struct IpHeader { uint8_t header_length:4; uint8_t version:4; uint8_t type_of_service; u16be total_length; u16be identificator; // Flags uint8_t _reserved:1; uint8_t dont_fragment:1; uint8_t more_fragments:1; uint8_t fragment_offset_part1:5; uint8_t fragment_offset_part2; uint8_t time_to_live; uint8_t protocol; u16be checksum; // ... }; #pragma pack(pop)
Внимание собственно обращать на типы 2-байтовых полей - u16be . Теперь поля структуры не нуждаются ни в каких преобразованиях порядка байт. Остаются проблемы с fragment_offset , ну а у кого их нет - проблем-то. Тем не менее тоже можно придумать шаблон, прячущий это безобразие, один раз его оттестировать и смело использовать во всём своём коде.

«Язык С++ достаточно сложен, чтобы позволить нам писать на нём просто» Как ни странно - Я

З.Ы. Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP. Отговорите - пока не поздно!

Как вы уже наверное догадались, битовое поле - это просто массив битов фиксированного размера. Битовые поля удобно использовать для решения задач связанных с булевой логикой. Например, представление цветов или шифрование данных, ну или просто перевод чисел в двоичную систему исчисления, все это так или иначе связано с булевой логикой, а значит легко реализуемо с помощью битовых полей. В С++ для реализации булевой логики, раньше использовались побитовые операции & , | , ~ и тип данных int , но это было неэффективно, по нескольким причинам: неоправданный расход памяти для хранения одного бита и конечно же не всегда удобно использовать поразрядные логические операции.

Итак, чтобы воспользоваться классом bitset , достаточно подключить заголовочный файл :

#include

Класс bitset это шаблон, который принимает один параметр - размер поля в битах. Отсюда следует запомнить, что любой объект типа bitset всегда имеет фиксированную длину. Вот пример:

Bitset<8> number(34);

В этом примере объявлено битовое поле - number размером 8 бит, которое инициализировано значением 34 . Давайте рассмотрим полноценный пример в котором показываются преимущества класса bitset при вводе/выводе битовых полей.

#include #include // заголовочный файл битовых полей #include // для манипулятора setw() using namespace std; int main() { bitset<8> number; cout << "Двоичное представление некоторых чисел:\n"; for(int i = 0; i < 21; i++) { number = i; cout << setw(2) << number.to_ulong() << " = " << number << endl; } return 0; }

Сразу смотрим результат работы программы:

Двоичное представление некоторых чисел: 0 = 00000000 1 = 00000001 2 = 00000010 3 = 00000011 4 = 00000100 5 = 00000101 6 = 00000110 7 = 00000111 8 = 00001000 9 = 00001001 10 = 00001010 11 = 00001011 12 = 00001100 13 = 00001101 14 = 00001110 15 = 00001111 16 = 00010000 17 = 00010001 18 = 00010010 19 = 00010011 20 = 00010100

В строке 2 , мы как всегда подключаем заголовочный файл bitset , для работы с битовыми полями. В восьмой строке, мы объявили битовое поле, размером 8 бит или - один байт. Далее в цикле мы выводим на экран двоичное представление чисел, начиная с 0 и заканчивая числом 20. Обратите внимание, что для вывода в двоичном формате никаких методов вызывать не надо, так как числа в битовых полях при инициализации сразу переводятся в двоичный формат. Также стоит обратить внимание на метод to_ulong() , он переводит двоичное представление числа в десятичное. Это хорошо видно в выводе результата программы.

Вот еще один пример программы, демонстрирующий некоторые операции класса bitset:

#include #include // заголовочный файл битовых полей using namespace std; int main() { int number; cout << "Введите целое число от 1 до 255: "; cin >> number; bitset<8> message(number); cout << number << " = " << message << endl; bitset<8> bit2 = message; message = message.flip(); // поменять все биты на противоположные cout << "Инвертированное число: " << message << endl; bitset<8> bit3 = bit2 | message; cout << bit2 << " | " << message << " = " << bit3 << endl; // операция логического ИЛИ bitset<8> bit4 = bit3 & message; cout << bit3 << " & " << message << " = " << bit4 << endl; // операция логического И bitset<8> bit5 = bit3 ^ message; cout << bit3 << " ^ " << message << " = " << bit5 << endl; // операция исключающего ИЛИ return 0; }

Из нового функционала стоит отметить метод flip() , он инвертирует все биты поля на противоположные, строка 15 . Так же в программе показаны пример использования логических операций | & ^ , смотреть строки 19, 22, 25 . Результат программы показан ниже:

битовых полей на языке C++.

В отличие от других языков программирования С++ имеет структуру, называемую битовыми полями ( полями битов ), которая позволяет работать с отдельными битами. Битовое поле – это особый компонент структуры, определяющий длину отдельного ее элемента. Битовые поля полезны по нескольким причинам.

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

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

Битовое поле – это элемент структуры, определенный как некоторое число битов, обычно меньшее, чем число битов в целом числе (оно по величине не превосходит машинного слова и зависит от реализации компилятора). Они предназначены для экономного размещения в памяти данных небольшого диапазона, обеспечивают удобный доступ к отдельным битам данных. Кроме того, с помощью битовых полей можно формировать объекты с длиной внутреннего представления, не кратной байту.

Битовые поля обычно применяются в низкоуровневом программировании.

Объявление битовых полей

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

Синтаксис объявления типа структуры с битовыми полями :

struct [ИмяСтруктуры] { Тип1 ИмяПоля1: ШиринаПоля1 Тип2 ИмяПоля2: ШиринаПоля2 ........................ ТипN ИмяПоляN: ШиринаПоляN } ИмяСтруктуры;

где struct – спецификатор типа;

ИмяСтруктуры – идентификатор ;

Тип1, ... ТипN – тип поля, который может быть только int , возможно, со спецификатором unsigned или signed ;

ШиринаПоля (длина) – целое неотрицательное десятичное число, значение которого обычно (в зависимости от реализации компилятора) не должно превышать длины машинного слова.

Например:

struct { int c1: 4; int c2: 12; } ab;

Битовые поля длиной 1 должны объявляться как unsigned , поскольку 1 бит не может иметь знака. Битовые поля могут иметь длину от 1 до 16 бит для 16-битных сред и от 1 до 32 бит для 32-битных сред.

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

Например, если нам нужны только биты cts и dsr , то можно объявить структуру status_type следующим образом:

struct status_type { unsigned: 4; unsigned cts:1; unsigned dsr:4; } status;

В структуре можно смешивать "обычные" элементы с битовыми полями .

Например:

struct emp { struct addr address; float pay; unsigned lay_off: 1; //работает или нет unsigned hourly: 1;//почасовая оплата или оклад unsigned deductions: 3; //удержание налога };

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

Вместо служебного слова struct можно употреблять



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: