Объединения и битовые поля
Объединения
Объединения в си похожи на структуры, с той разницей, что все поля начинаются с одного адреса. Это значит, что размер объединения равен размеру самого большого его поля. Так как все поля начинаются с одного адреса, то их значения перекрываются. Рассмотрим пример:
#include <conio.h>
#include <stdio.h>
union Register32 {
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
struct {
unsigned short low;
unsigned short high;
} words;
unsigned dword;
};
typedef union Register32 EAX;
void main() {
EAX reg;
reg.dword = 0x0000C0FF;
printf(" dword \t%08x\n", reg.dword);
printf(" low word \t%04x\n", reg.words.low);
printf("high word \t%04x\n", reg.words.high);
printf(" byte1 \t%02x\n", reg.bytes.byte1);
printf(" byte2 \t%02x\n", reg.bytes.byte2);
printf(" byte3 \t%02x\n", reg.bytes.byte3);
printf(" byte4 \t%02x\n", reg.bytes.byte4);
getch();
}
Здесь было создано объединение, которое содержит три поля – одно поле целого типа (4 байта), два поля типа short int (2 байта каждое) и 4 поля по одному байту. После того, как значение было присвоено полю dword, оно также стало доступно и остальным полям.
Напоминаю, что на x86 байты располагаются справа налево. Все поля объединения "обладают" одинаковыми данными, но каждое поле имеет доступ только до своей части.
Вот ещё один пример: рассмотрим представление числа с плавающей точкой:
#include <conio.h>
#include <stdio.h>
union floatint{
float f;
int i;
};
void main() {
union floatint u = {10.f};
printf("%f\n", u.f);
printf("%x\n", u.i);
getch();
}
Обратите внимание, что объединение можно инициализировать, как и структуру. При этом значение будет приводиться к типу, который имеет самое первое поле. Сравните результаты работы
#include <conio.h>
#include <stdio.h>
union floatint{
int i;
float f;
};
void main() {
union floatint u = {10.f};
printf("%f\n", u.f);
printf("%x\n", u.i);
getch();
}
Битовые поля
Битовые поля в си объявляются с помощью структур. Они позволяют получать доступ до отдельных битов или групп битов. Доступ до отдельных битов можно осуществлять и с помощью битовых операций, но использование битовых полей часто упрощает понимание программы.
Синтаксис объявления битового поля
struct <���������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
���ом примере каждое поле структуры обозначено как битовое поле, длина каждого поля равна единице. Обращаться к каждому полю можно также, как и к полю обычной структуры. Битовые поля имеют тип unsigned int, так как имеют длину один бит. Если длина поля больше одного бита, то поле может иметь и знаковый целый тип.
#include <conio.h>
#include <stdio.h>
struct byte {
signed char a: 4;
signed char b: 4;
};
void main() {
struct byte x = {-1, 3};
printf("hex a = %x\n", x.a);
printf("dec a = %d\n", x.a);
printf("hex b = %x\n", x.b);
printf("dec b = %d\n", x.b);
getch();
}
Размер структуры, содержащей битовые поля, всегда кратен 8. То есть, если одно поле содержит 5 бит, а второе 4, то второе поле начинается с восьмого бита и три бита остаются неиспользованными.
Неименованное поле может иметь нулевой размер. В этом случае следующее за ним поле смещается так, чтобы добрать до 8 бит.
Если же адрес поля уже кратен 8 битам, то нулевое поле не добавит сдвига.
Кроме того, если имеются обычные поля и битовые поля, то первое битовое поле будет сдвинуто так, чтобы добрать до 8 бит.
#include <conio.h>
#include <stdio.h>
struct byte1 {
char a;
char b;
char c;
};
struct byte2 {
char a;
char b;
char c;
unsigned d: 1;
};
struct byte3 {
char a;
char b;
char c;
unsigned d: 1;
unsigned: 0; //неименованое поле нулевого размера
unsigned e: 1;
};
void main() {
printf("sizeof byte1 = %d\n", sizeof(struct byte1));
printf("sizeof byte2 = %d\n", sizeof(struct byte2));
printf("sizeof byte3 = %d", sizeof(struct byte3));
getch();
}
В этих примерах видно, что структура добирает даже не до 8 бит, а больше - до адреса, кратного 4 байтам. Работать подобным образом, инициализируя каждое поле по отдельности, неудобно. Поэтому структуры с битовыми полями делают полем объединения, например:
#include <conio.h>
#include <stdio.h>
struct byte {
unsigned a0: 1;
unsigned a1: 1;
unsigned a2: 1;
unsigned a3: 1;
unsigned a4: 1;
unsigned a5: 1;
unsigned a6: 1;
unsigned a7: 1;
};
union Byte {
unsigned char value;
struct byte bitfield;
};
void main() {
union Byte x;
x.value = 10;
printf("%d%d%d%d%d%d%d%d", x.bitfield.a7, x.bitfield.a6, x.bitfield.a5, x.bitfield.a4, x.bitfield.a3,
x.bitfield.a2, x.bitfield.a1, x.bitfield.a0);
getch();
}
Те же самые действия можно было сделать и с помощью обычного сдвига
#include <conio.h>
#include <stdio.h>
void main() {
unsigned char c = 10;
int i;
for (i = 7; i > -1; i--) {
printf("%d", (c >> i) & 1);
}
getch();
}
Рассмотрим ещё один пример – знакопостоянный сдвиг вправо. Сдвиг вправо (>>) выталкивает самый левый бит и справа записывает ноль. Из-за этого операцию сдвига вправо нельзя применить, например, для чисел со знаком, так как будет потерян бит знака. Исправим ситуацию, сделаем знакопостоянный сдвиг: будем проверять последний бит числа (напомню, что мы работаем с архитектурой x86 и биты расположены «задом наперёд»)
int int_signed_shiftr(int32_t x) {
union {
int32_t int_f;
struct {
unsigned char: 8;
unsigned char: 8;
unsigned char: 8;
unsigned char: 7;
unsigned char le_sign: 1;
} bitfield;
} iss;
unsigned sign_bit;
iss.int_f = x;
//Сохраняем бит перед сдвигом
sign_bit = iss.bitfield.le_sign;
iss.int_f = iss.int_f >> 1;
//Возвращаем значение бита обратно
iss.bitfield.le_sign = sign_bit;
return iss.int_f;
}
Здесь я специально использовал тип int32_t (библиотека stdint.h), чтобы гарантировать размер переменных в 32 бита. Теперь можно вызвать функцию и посмотреть результат.
void main() {
printf("%d\n", int_signed_shiftr(22));
printf("%d\n", int_signed_shiftr(-22));
printf("%d\n", int_signed_shiftr(25));
printf("%d\n", int_signed_shiftr(-25));
printf("%d\n", int_signed_shiftr(27));
printf("%d\n", int_signed_shiftr(-27));
getch();
}
Вывод
11
-11
12
-13
13
-14
