Многие задачи требуют создания минимальных по размеру программ. Один из
способов экономии памяти в программах на Си заключается в использовании битовых
полей для представления совокупности данных целого типа. Битовое поле может
иметь тип либо signed int либо unsigned int и занимать от 1 до 16 битов.

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

обеспечивает размещение в памяти структурной переменной flags,
включающей в себя битовые поля с именами a, b, c и d, как показано на рис. 9.3.

Рис. 9.3. Размещение в памяти битовых полей.

Поля не могут переходить через границы памяти целого числа. Например,
если бы поле d имело размер более 5-ти битов, то оно было бы размещено в
следующем слове. Для принудительного выравнивания к границе следующего слова
используется размер 0. Если в рассматриваемой структуре BITFLDS описать поле
без имени int:3; как int:0; , то поле d будет размещено в новом слове.
Битовые поля обрабатываются как целые укороченного размера. Их можно
использовать в любых выражениях и с любыми операциями за исключением операции
взятия адреса. Обращение к полям осуществляется аналогично обращению к другим
элементам структуры, то есть с использованием операции точка, например так:

if(flags.b == flags.d)...

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

struct { unsigned shift:1;

unsigned cups:1;

позволяет хранить
информацию о состоянии клавиатуры компьютера. При этом включение и выключение
признаков осуществляется понятной операцией присваивания (в другом случае
пришлось бы пользоваться логическими операциями):

а проверка состояния
переключателей - условным оператором:

if(key.shift==1 && key.cups==1)...

Заметим, что битовое поле внутри структурного шаблона не может быть
массивом, хотя описание массива структур, каждый элемент которого может
содержать битовые поля, вполне обосновано и не противоречит синтаксису Турбо
Си. Используя ранее приведенный структурный шаблон BITFLDS, можно сделать
описание:

struct BITFLDS system;

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

struct COORDINATES {

float a; /* Длина */

float b; /* Ширина */

float c; /* Высота */

char name; /* Шифризделия */

struct COORDINATES point;

struct DETAL rez, /* Резисторы */

cond; /* Конденсаторы */

и доступ к битовому
полю осуществляется также, как и в случае вложенных структурных переменных,
например, rez.point.x

Битовое поле

Битовое поле - в программировании число, занимающее некоторый набор битов, напрямую не адресуемый процессором. Например: при 8-битном байте первые два поля протокола - версия и IHL - будут битовыми полями. На машинах с 32-битным байтом все поля IP-пакета (кроме IP-адресов отправителя и получателя) будут битовыми.

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

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

Операции над многобитовыми полями

Пусть в одном байте находятся три битовых поля: 1-битовые a и b , 2-битовое c и 4-битовое d , то есть .

[старший бит] d d d d c c b a [младший бит]

Например: при a =1, b =0, c =2=10 2 , d=5=0101 2 получаем x =01011001 2 =89.

Сборка одного числа из битовых полей

Двоичные компьютеры обычно имеют команды побитового сдвига, которые позволяют быстро умножать на степени двойки - 2, 4, 8 и т. д. Вместо сложения можно применить команду логического «ИЛИ». Таким образом, число x можно собрать и по-другому:

X = (d << 3) | (c << 2) | (b << 1) | a

Извлечение битового поля

Для извлечения битового поля нужно провести две операции:

  1. Умножить операцией логического «И» число на битовую маску - число, у которого в соответствующих разрядах единицы, а в остальных нули.

C = (x & 00001100b) >> 2

  1. Провести побитовый сдвиг вправо.
  2. Умножить операцией логического «И» число на битовую маску соответствующей длины.
c = (x >> 2) & 00000011b

Для младшего поля побитовый сдвиг не нужен, то есть:

A = x & 00000001b

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

D = x >> 4

Замена битового поля

  1. Очистить x от предыдущего значения побитовым умножением на маску с нулями в соответствующих битах.
  2. Побитово сложить x с новым значением (сдвинутым на нужное количество битов)

Например, если нам нужно заменить d , то

Xnew = (x & 00001111b) | (d << 4)

Операции над однобитовыми полями

Поля a и b имеют длину 1 бит - это позволяет работать с ними несколько другими средствами.

Проверка отдельного бита

Для проверки надо побитово умножить x операцией «И» на маску, у которой одна единица - в соответствующей позиции. Если получился 0, бит равен 0.

B = ((x & 00000010b) != 0)

Проверка, равен ли единице хотя бы один бит из нескольких:

A_or_b = ((x & 00000011b) != 0)

Проверка, равны ли единице все биты из нескольких:

A_and_b = ((x & 00000011b) == 00000011b)

Установка битов

Для этого надо сложить операцией «ИЛИ» x с маской, у которой единицы в соответствующих позициях. Например, чтобы включить бит a :

X1 = x | 00000001b

Чтобы включить и a , и b :

X2 = x | 00000011b

Снятие битов

Чтобы снять один или несколько битов, надо сложить x операцией «И» с маской, у которой в соответствующих позициях нули. В частности, чтобы выключить бит b , нужно дать команду:

X3 = x & 11111101b

Переключение битов

Для переключения битов (с 0 на 1, с 1 на 0) надо сложить x командой «Исключающее ИЛИ » с маской, у которой в соответствующих позициях единицы. Например, бит b переключается так:

X4 = x ^ 00000010b

Операции над знаковыми полями в дополнительном коде

Существуют два способа хранения отрицательных целых чисел - знаковый бит и дополнительный код . В подавляющем большинстве современных машин применяется второй. При записи отрицательных чисел дополнительным кодом имеем:

1 = 11111111b -2 = 11111110b -3 = 11111101b -4 = 11111100b и т. д.

Считаем, что поля c и d имеют именно такой формат. Тогда поле c может хранить числа от −2=10 2 до 1=01 2 , а поле d - от −8=1000 2 до 7=0111 2 .

Сборка и замена чисел

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

X = (d << 4) + ((c & 00000011b) << 2) + (b << 1) + a

Извлечение чисел

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

C = (x << 4) >>a 6 d = x >>a 4

Внимание! В языке программирования Java всё наоборот: знаком >> обозначается арифметический сдвиг, знаком >>> - простой.

Если арифметического сдвига нет, то…

C1 = x >> 2 если (c1 & 00000010b ≠ 0) то c = c1 | 0x11111100b иначе c = c1 & 0x00000011b

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

В языке C/C++

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

Struct rgb { unsigned r:3; unsigned g:3; unsigned b:3; };

Примечания


Wikimedia Foundation . 2010 .

Смотреть что такое "Битовое поле" в других словарях:

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

    дополнительное битовое поле - Битовое поле, которое иногда вставляется (для кодирования абстрактного значения), а иногда опускается (МСЭ Т Х.692). Тематики электросвязь, основные понятия EN optional bit field … Справочник технического переводчика

    Поле многозначное понятие, связанное с протяжённостью в пространстве: Понятие в математике Поле алгебраическая структура с двумя операциями. Скалярное, векторное и тензорное поля в дифференциальной геометрии (см. вектор и тензор) и матанализе … Википедия

    поле маршрутной информации - Битовое поле, содержащее маршрутную информацию. Тематики сети вычислительные EN routing information fieldRIF … Справочник технического переводчика - Битовое поле, которое указывает, присутствует ли дополнительное битовое поле (МСЭ Т Х.692). Тематики электросвязь, основные понятия EN presence determinant … Справочник технического переводчика

    детерминант выбора - Битовое поле, которое определяет, какие из нескольких возможных кодовых последовательностей (каждая из которых представляет разные абстрактные значения) имеются в некотором другом битовом поле (МСЭ Т Х.692). { Тип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 можно употреблять

    Недавно познакомился со структурами 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. Отговорите - пока не поздно!