Битовые поля

Недавно познакомился со структурами 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 , signed или unsigned . (Кроме того, в соответствии со стандартом С99, у битового поля еще может быть тип _Вооl .)

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



Информацию в байте состояния можно представить с помощью следующего битового поля:

Struct status_type { unsigned delta_cts: 1; unsigned delta_dsr: 1; unsigned tr_edge: 1; unsigned delta_rec: 1; unsigned cts: 1; unsigned dsr: 1; unsigned ring: 1; unsigned rec_line: 1; } status;

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

Status = get_port_status(); if(status.cts) printf("Разрешение на передачу"); if(status.dsr) printf("Данные готовы");

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

Status.ring = 0;

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

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

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

Кроме того, обратите внимание, что если биты, расположенные после dsr , не используются, то определять их не надо.

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

Struct emp { struct addr address; float pay; unsigned lay_off: 1; /* временно уволенный или работающий */ unsigned hourly: 1; /* почасовая оплата или оклад */ unsigned deductions: 3; /* налоговые (IRS) удержания */ };

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

Использование битовых полей имеет определенные ограничения. Нельзя получить адрес битового поля. Нет массивов битовых данных. При переносе кода на другую машину неизвестно, будут ли поля обрабатываться справа налево или слева направо; это значит, что выполнение любого кода, в котором используются битовые поля, в определенной степени может зависеть от машины, на которой он выполняется. Другие ограничения будут зависеть от конкретных реализаций.

Называются также полями битов .

Рассматривается реализация структурных типов данных union , enum , и struct . Так же рассматривается заполнение структур и реализацию битового поля.

Unions (обьединение)

Когда используется доступ к полю union другого типа, результат может быть предсказан на основе исходного типа.

Enumerations (перечисление)

Обьект enum реализуется наименьшим числовым типом который содержит диапазон перечисления enum .

В C режиме, и в C++ режиме без --enum_is_int , если enum перечисление содержит только положительные значения перечислителя, тип хранения перечисления является первым беззнаковым типом из следующего списка в соответствии с диапазоном перечислений в перечислении enum .В других режимах, и в случаях когда enum содержит отрицательные значения перечислителя,тип хранения enum является первым из следующего, согласно диапазону перечисления в перечислителе enum :

  • unsigned char если не используется --enum_is_int
  • signed char если не используется --enum_is_int
  • unsigned short если не используется --enum_is_int
  • signed short если не используется --enum_is_int
  • signed int
  • unsigned int кроме Си с --strict
  • signed long long кроме Си с --strict
  • unsigned long long кроме Си с --strict .

Примечание

  • В RVCT 4.0, тип хранилища enum являющийся первым беззнаковым типом из списка применяется только в режиме GNU (--gnu).
  • В ARM ® Compiler 4.1 и выше, тип хранения перечисления enum тип хранения перечисления, являющийся первым беззнаковым типом из списка, применяется независимо от режима.

Реализация enum таким образом может уменьшать размер данных. Опция командной строки --enum_is_int lделает основной тип enum наименьшой шириной int .

See the description of C language mappings in the Procedure Call Standard for the ARM ® Architecture specification for more information.

Примечание

Care must be taken when mixing translation units that have been compiled with and without the --enum_is_int option, and that share interfaces or data structures.

In strict C, enumerator values must be representable as int s. That is, they must be in the range -2147483648 to +2147483647, inclusive. A warning is issued for out-of-range enumerator values:

#66: enumeration value is out of "int" range

Such values are treated the same way as in C++, that is, they are treated as unsigned
int , long long , or unsigned long
long .

To ensure that out-of-range Warnings are reported, use the following command to change them into Errors:

armcc --diag_error=66 ...

Structures (структуры)

Следующие пункты относятся к:

  • все структуры Си
  • все C++ структуры и классы не использующие виртуальные или базовые классы.

Структуры могут содержать заполнение для правильного выравнивания полей и корректного выравнивания структуры.

На графике 1 показан пример классической неупакованной структуры.

Байты 1, 2, и 3 заполняются чтобы обеспечить правильное выравнивание поля.

Байты 11 и 12 заполняются чтобы обеспечить правильное выравнивание структуры.

Функция sizeof() возвращает размер структуры включающая заполнение.

График 1 Пример обычной неупакованной структуры

Компилируются структуры одним из способов в зависимости от ее определения:

  • Структуры которые определены как static или extern заполняются нулями.
  • Структуры в стеке или куче, например которые определены с помощью malloc() или auto , заполняются тем что хранилось в памяти ранее. Вы не можете использовать функцию memcmp() для сравнения содержимого структур определенных таким образом.

Используйте опцию --remarks для просмотра сообщений генерируемых компилятором когда он вставляет дополнения в структуру struct .

Структуры с пустой инициализацией разрешены в C++:

Struct { int x; } X = { };

Однако, если вы компилируете C или C++ с параметрами — cpp и — c90, генерируется ошибка.

Bitfields (битовое поле)

В неупакованных структурах, ARM компилятор выделяет битовые поля в контейнеры.

Контейнер как корректно выравненный обьект декларируемого типа.

Битовые поля распределяются таким образом, чтобы указанное первое поле занимало младшие биты слова в зависимости от конфигурации::

Little-endian Самые низкие адресации являются наименее значимыми. Big-endian Самые низкие адресации являются наиболее значимыми.

Контейнер битового поля может быть любым целым типом.

Примечание

In strict 1990 ISO Standard C, the only types permitted for a bit field are int , signed int , and unsigned int . For non- int bitfields, the compiler displays an error.

В строгом стандарте ISO стандартного стандарта 1990 года единственными типами, разрешенными для битового поля, являются int , signed int , и unsigned int . Для не — int битового поля компилятор отображает ошибку.

Простое битовое поле, обьявленное без signed или unsigned квалификаторов, рассматривается как unsigned . Для примера, int x:10 выделяется как целое число без знака размером 10 бит.

Битовое поле присваивается первому контейнеру правильного типа, который имеет достаточное количество нераспределенных битов, например:

Struct X { int x:10; int y:20; };

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

Битовое поле полностью содержится в контейнере. Битовое поле, которое не помещается в контейнер, помещается в следующий контейнер того же типа. Например, декларация z переполняет контейнер, если для структуры объявлено дополнительное битовое поле:

Struct X { int x:10; int y:20; int z:5; };

Компилятор заполняет оставшиеся два бита для первого контейнера и назначает новый целочисленный контейнер для z .

Контейнеры битого поля могут перекрывать друг друга, например:

Struct X { int x:10; char y:2; };

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

Поскольку естественное выравнивание символа равно 1, компилятор выполняет поиск первого байта, который содержит достаточное количество нераспределенных битов, чтобы полностью содержать бит. В структуре примера второй байт контейнера int имеет два бита, выделенных для x , и 6 бит нераспределены. Компилятор выделяет контейнер char начиная со второго байта предыдущего int контейнера, пропускает первые два бита, которые выделены для x , и выделяет два бита в y .

Если y объявлен char y: 8 , компилятор заполняет второй байт и выделяет новый контейнер символов в третий байт, потому что битовое поле не может переполнять его контейнер. На следующем рисунке показано распределение битового поля для следующего примера структуры:

Struct X { int x:10; char y:8; };

Распределение битового поля

Примечение

Те же основные правила применяются к объявлениям битового поля с разными типами контейнеров. Например, добавим битовое поле int к структуре и она примет следующий вид:

Struct X { int x:10; char y:8; int z:5; }

Компилятор выделяет контейнер int , начинающийся в том же месте, что и контейнер int x:10 , и выделяет байт-выровненный char и 5-битное битовое поле как показано ниже:

Распределение битового поля

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

Примечание

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

Packing and alignment of bitfields (упаковка и выравнивание битовых полей)

Использование __attribute__((aligned(n))) делает битовое поле n-байт выравненным, не только его контейнер, but the bitfield is aligned to the packed alignment at most. It is ignored on bitfields in __packed and __attribute__((packed)) structs.

The alignment of a bitfield member’s container is the same as the alignment of that bitfield member. The size of a bitfield container is the least multiple of the alignment that fully covers the bitfield, but no larger than the size of the container-type. The following code examples show this:

#pragma pack(2) /* Контейнер b должен начинаться с границы выравнивания 2 байта и должен * иметь размер не больше, чем тип контейнера, в этом случае размер * короткий. Контейнер b не может начинаться со смещения 0 (перекрывается с a) *, так как бит b будет начинаться со смещения 2 и не будет полностью лежать * внутри контейнера. Поэтому контейнер для b должен начинаться со смещения * 2. * * Data layout: 0x11 0x00 0x22 0x22 * Container layout:| a | | b | */ struct { char a; short b: 16; } var1 = { 0x11, 0x2222 }; /* контейнер b может быть до 4 байт. Его размер должен быть 2 или 4 байта, так как они являются кратны* ми выравниванию, которые не больше размера контейнера. При использовании 4 байтового контейнера *, начинающегося с 0, битовое поле b может начинаться со смещения 1 и полностью лежать * внутри контейнера. * * Data layout: 0x11 0x22 0x22 0x00 * Container layout:| a | * | b | */ struct { char a; int b: 16; } var2 = { 0x11, 0x2222 };

Упакованные контейнеры битовых полей, включая все контейнеры битовых полей в упакованных структурах, имеют выравнивание 1. Поэтому максимальное битовое дополнение, вставленное для выравнивания упакованного контейнера битовых полей, составляет 7 бит.

Для неупакованного контейнера битового поля, максимальное заполнение битов 8*sizeof(container-type)-1 .

Заполнение хвоста всегда вставляется в структуру по мере необходимости, чтобы гарантировать, что массивы структуры правильно выровнены.

A packed bitfield container is only large enough (in bytes) to hold the bitfield that declared it. Non-packed bitfield containers are the size of their type.

Следующие примеры иллюстрируют эти взаимодействия.

Struct A { int z:17; }; // sizeof(A) = 4, alignment = 4 struct A { __packed int z:17; }; // sizeof(A) = 3, alignment = 1 __packed struct A { int z:17; }; // sizeof(A) = 3, alignment = 1 struct A { char y:1; int z:31; }; // sizeof(A) = 4, alignment = 4 struct A { char y:1; __packed int z:31; }; // sizeof(A) = 4, alignment = 1 __packed struct A { char y:1; int z:31; }; // sizeof(A) = 4, alignment = 1 struct A { char y:1; int z:32; }; // sizeof(A) = 8, alignment = 4 struct A { char y:1; __packed int z:32; }; // sizeof(A) = 5, alignment = 1 __packed struct A { char y:1; int z:32; }; // sizeof(A) = 5, alignment = 1 struct A { int x; char y:1; int z:31; }; // sizeof(A) = 8, alignment = 4 struct A { int x; char y:1; __packed int z:31; }; // sizeof(A) = 8, alignment = 4 __packed struct A { int x; char y:1; int z:31; }; // sizeof(A) = 8, alignment = 1 struct A { int x; char y:1; int z:32; }; // sizeof(A) = 12, alignment = 4 struct A { int x; char y:1; __packed int z:32; }; // sizeof(A) = 12, alignment = 4 __packed struct A { int x; char y:1; int z:32; }; // sizeof(A) = 9, alignment = 1

Note that and are not identical; the location of z within the structure and the tail-padding differ.

Struct example1 { int a: 8; /* 4-byte container at offset 0 */ __packed int b: 8; /* 1-byte container at offset 1 */ __packed int c: 24; /* 3-byte container at offset 2 */ }; /* Total size 8 (3 bytes tail padding) */; struct example2 { __packed int a: 8; /* 1-byte container at offset 0 */ __packed int b: 8; /* 1-byte container at offset 1 */ int c: 8; /* 4-byte container at offset 0 */ }; /* Total size 4 (No tail padding) */ struct example3 { int a: 8; /* 4-byte container at offset 0 */ __packed int b: 32; /* 4-byte container at offset 1 */ __packed int c: 32; /* 4-byte container at offset 5 */ int d: 16; /* 4-byte container at offset 8 */ int e: 16; /* 4-byte container at offset 12 */ int f: 16; /* In previous container */ }; /* Total size 16 (No tail padding) */

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

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

  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;
};

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



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

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

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