Программирование на языке C в Microsoft Visual Studio 2010

Операции с разрядами (битами) в языке С

Разбить на страницы
Показывать лекцию целиком

Теоретическая часть

16.1. Базовые системы счисления

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

У бита возможны только два значения: 0 и 1. Техническая реализация таких состояний легко реализуется, например, включено–выключено, положительное значение напряжения–отрицательное значение напряжения (определенного уровня) и т.д.

В языке программирования С термин байт используется для обозначения размера (разрядности) хранения набора символов. Поэтому в языке С байт может содержать 8, 9, 16 и другое количество разрядов. Однако в характеристиках модулей памяти и систем передачи данных предполагается, что байт содержит восемь разрядов [16.1].

Разряды байта пронумерованы справа налево числами от 0 до 7. Седьмой разряд (крайний левый) называется старшим, а нулевой разряд (крайний правый) – младшим.

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

128 64 32 16 8 4 2 1 = 255
27 26 25 24 23 22 21 20 = 255

Наименьшему значению соответствует комбинация нулей 00000000, которая представляет собой просто нуль [9].

Байт может хранить числа от 0 до 255, что составляет 256 возможных значений. Программа может интерпретировать комбинацию разрядов иначе и применять байт для хранения чисел от –128 до 127, что составляет 256 возможных значений. Например, тип unsigned char обычно характеризуется использованием байта для представления чисел в диапазоне от 0 до 255, тип signed char – до 127.

В основании восьмеричной системы счисления лежит число 8 (23). Каждое знакоместо восьмеричного числа соответствует определенной степени восьми. Для записи используются цифры от 0 до 7. Каждая цифра восьмеричного числа соответствует трем двоичным цифрам. Двоичные эквиваленты восьмеричных цифр представлены в табл. 16.1.

Двоичные эквиваленты восьмеричных цифр
Восьмеричная цифра Двоичный эквивалент
0 000
1 001
2 010
3 011
4 100
5 101
6 110
7 111

В шестнадцатеричной системе счисления используются степени числа 16 и цифры от 0 до 15. Для представления цифр, соответствующих десятичным значениям от 10 до 15, используются буквы от A до F [16.1]. Например, шестнадцатеричное число A3F (в языке С записывается как 0xA3F ) представляет значение

$$10\times16^2 + 3\times16^1 + 15\times16^0 = 2 623$$ (по основанию 10).

В приведенной записи числу 10 соответствует А, а числу 15 – F.

Язык С допускает использование букв нижнего и верхнего регистра (строчные и прописные буквы) для обозначения шестнадцатеричных цифр. Таким образом, число 2 623 в шестнадцатеричной системе счисления можно записать как 0xA3F, так и 0xa3f.

Каждая цифра шестнадцатеричного числа соответствует 4-значному двоичному числу [16.1]. Поэтому две шестнадцатеричных цифры соответствуют одному восьмиразрядному байту. Первая цифра представляет 4 старших разряда, а вторая цифра – 4 младших разряда.

Соответствие между шестнадцатеричными цифрами, десятичными и двоичными числами показано в табл. 16.2.

Эквиваленты шестнадцатеричных чисел
Десятичное число Шестнадцатеричная цифра Двоичный эквивалент
1 2 3
0 0 0000
1 1 0001
2 2 0010
3 3 0011
4 4 0100
5 5 0101
6 6 0110
7 7 0111
8 8 1000
9 9 1001
10 A 1010
11 B 1011
12 C 1100
13 D 1101
14 E 1110
15 F 1111

В языке С существуют два средства управления разрядами [16.1]. Первое представляет собой набор поразрядных операций, а второе – форму полей данных, которое предоставляет доступ к разрядам значения типа int.

Далее будут использоваться 8-разрядные числа в двоичной системе счисления.

16.2. Логические поразрядные операции

В языке программирования С существуют два вида поразрядных операций: логические операции и операции сдвига [16.1].

Поразрядные операции выполняются над каждым разрядом независимо от разрядов слева или справа. Поразрядные операции выполняются над целыми числами. Рассмотрим логические поразрядные операции.

Унарная операция $$\sim$$ преобразовывает все единицы в нули и все нули в единицы (предполагается, что операции производятся над двоичными числами). Данную операцию называют также операцией "дополнение", т.е. когда все биты, равные 0, устанавливаются в 1, а когда все биты, равные 1, – устанавливаются в 0.

Поразрядная операция И обозначается символом .

Двоичная операция создает новое значение за счет выполнения поразрядного сравнения двух операндов. Для каждой позиции результирующий разряд будет иметь значение 1 только в случае, когда соответствующие разряды обоих операндов имеют значение 1. Можно сказать, что когда над двумя значениями производится операция поразрядного умножения , то двоичные представления чисел сравниваются бит за битом [2]. Например, пусть одна переменная w1 есть число 25, а другая w2 – число 77. Соответственно в двоичном представлении 25 = 0000000000011001, 77 = 0000000001001101. Тогда в результате поразрядной операции получим число w3 = w1 w2. Результат действия оператора можно представить следующим образом:

Поразрядная операция И ()
w1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 (25)
w2 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 (77)
w3 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 (9)

В результате поразрядной операции "" над двумя числами (25 и 77 в двоичном представлении), которые имеют, например, 16 бит, получили новое число, т.е. 9.

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

w3 = w1  3;

переменной w3 присваивается значение переменной w1, для которой выполнена поразрядная операция со значением 3. При этом все биты левее двух младших битов устанавливаются в 0, а остальные биты сохраняют свое значение [16.2]. Маска представляет собой некоторую комбинацию разрядов [16.1]. Разряды маски с нулевыми значениями можно считать аналогом непрозрачных ячеек реальной маски, а разряды со значениями 1 – прозрачными ячейками.

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

Поразрядная операция ИЛИ обозначается символом |.

Когда над двумя значениями производится операция поразрядно ИЛИ, то последовательно сравниваются значения всех битов при двоичном представлении этих значений [5]. Если при этом соответствующий бит имеет значение 1 в первом или втором операнде, то результирующее значение будет равно 1. Рассмотрим предыдущий пример с поразрядной операции ИЛИ:

Поразрядная операция ИЛИ (|)
w1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 (25)
w2 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 (77)
w3 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 1 (93)

Поразрядную операцию ИЛИ (|) обычно используют для установки заданных битов слова в 1. Логическую операцию ИЛИ называют также включающей дизъюнкцией, или логическим сложением. Часто применяется обозначение OR.

Поразрядная операция исключающего ИЛИ ( ^ ) работает следующим образом [5]. Сравниваются соответствующие биты двух операндов, и если только один из битов равен 1, то результат будет равен 1. А при равенстве обоих соответствующих битов или 0, или 1 результат будет равен 0. Для двух операндов b1, b2 при использовании исключающего ИЛИ ( ^ ) справедлива таблица истинности (табл. 16.3).

Таблица истинности операции исключающего ИЛИ ( ^ )
b1 b2 b1 ^ b2
0 0 0
0 1 1
1 0 1
1 1 0

Если операцию исключающего ИЛИ ( ^ ) использовать для одного и того же значения, то в результате будет получено нулевое значение. Этот прием часто использовался программистами на языке ассемблера как наиболее быстрый путь установить значение в нуль или сравнить два значения на их равенство. Этот способ не рекомендуется использовать в языке программирования С, так как при этом скорость работы не повышается, а программа становится менее понятной [16.2].

Операция исключающего ИЛИ ( ^ ) может применяться для перестановки значений двух переменных без выделения дополнительной памяти (и, соответственно, без использования дополнительной переменной).

Поразрядная операция исключающего "ИЛИ" называется также исключающей дизъюнкцией. Часто применяется обозначение XOR.

1.3. Поразрядные операции сдвига

Оператор сдвига влево: <<

Когда оператор сдвига влево ( << ) выполняется над некоторым значением, все биты, составляющие это значение, сдвигаются влево [16.2]. Связанное с этим оператором число показывает количество бит, на которое значение должно переместиться. Биты, которые сдвигаются со старшего разряда, считаются потерянными, а на место младших битов всегда помещаются нули.

Оператор сдвига вправо: >>

Операция сдвига вправо ( >> ) сдвигает разряды левого операнда вправо на количество позиций, указываемое правым операндом. Выходящие за правую границу разряды теряются. Для типов данных без знака ( unsigned ) освобождаемые слева позиции заполняются нулями. Для знаковых типов данных результат зависит от используемой системы. Освобождаемые позиции могут заполняться нулями либо копиями знакового (первого слева) разряда [16.1].

Поразрядные операции сдвига могут служить удобным и эффективным (в зависимости) средством выполнения операций умножения и деления на числа, представляющие собой степени двойки [16.1]. Такие операции аналогичны смещению десятичной точки при умножении или делении на 10.

1.4. Битовые поля

Второй метод управления разрядами состоит в использовании битового (разрядного) поля [9], которое представляет собой просто последовательную цепочку разрядов в рамках значения типа signed int или unsigned int.

Битовое поле может быть только элементом структуры или объединения и вне объектов этих типов не встречается [16.3].

Битовое поле создается путем объявления структуры (объединения), которая помечает каждое поле и определяет его разряд.

Приведем пример из [16.2] с использованием битовых полей в структуре:

struct packed_struct {
unsigned int  : 3;
unsigned int f1 : 1;
unsigned int f2 : 1;
unsigned int f3 : 1;
unsigned int type : 8;
unsigned int index : 18;
};

В созданном шаблоне-структуре с дескриптором (именем-этикеткой) packed_struct первый член не имеет имени. Символ , ":3" задает три безымянных бита. Второй, третий и четвертый члены структуры, f1, f2, f3, также имеют тип unsigned int. Символ ":1" говорит о том, что в данном члене структуры будет храниться 1 бит. Член структуры с именем type в памяти занимает 8 бит. Член структуры index рассчитан на хранение 18 бит.

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

struct packed_struct packed_data;

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

packed_data.type = 7;

Если ранее была объявлена какая-то переменная, например, n, то присвоение может быть таким:

packed_data.type = n;

При этом нет необходимости беспокоиться о том, что значение переменной n будет слишком большим. Только младшие 8 бит будут учитываться при присваивании значения для поля packed_data.type.

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

n = packed_data.type;

В приведенном примере после извлечения значения поля type будет произведен сдвиг в сторону младших бит.

Битовые поля могут быть объявлены только как тип int (в стандарте С99 также _Bool ). Если битовое поле имеет тип int, то этот знаковый ( signed ) или беззнаковый ( unsigned ) тип зависит от реализации.

Для исключения неоднозначности следует использовать явные объявления: signed int или unsigned int.

Битовые поля нельзя объединять в массивы. Нельзя использовать адрес битового поля, поэтому не может быть такого типа, как "указатель на битовое поле" [16.2]. Компилятор языка программирования С не переупорядочивает битовые поля для получения оптимального распределения памяти. Но в некоторых случаях может производиться выравнивание за счет безымянного поля. Это может использоваться для выравнивания следующего поля структуры по границе блока [16.2].

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

Практическая часть

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

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

Программный код решения примера:

#include <stdio.h>
#include <conio.h>

// Прототип функции
void printBits(unsigned int var);

// Главная функция
int main (void) {
	unsigned int number;

printf("\n The program on demonstration digit-by-digit operation of denying ( ~ )\n");

	printf("\n\t Enter a whole number of unsigned: ");
	scanf_s("%d", number);
	printf("\n\t Binary representation of the starting number and\n");
	printf("\t Binary representation of bitwise negation of the initial number:\n");
	printBits(number); //Исходное число
printBits(~number); // Число после поразрядного дополнения

	printf("\n\n Press any key: ");
	_getch();
	return 0;
}

// Функция побитового представления целого числа без знака
void printBits(unsigned int var)
{
unsigned int b;
unsigned int mask = 1 << 31; // shift to 31 bit
printf("\n\t %10u = ", var);
for (b = 1; b <= 32; ++b) {
	printf("%c", var  mask ? '1' : '0');
	var <<= 1; // or: var = var << 1;
	if (b % 8 == 0)
		putchar(' ');
}
}

В программе применен форматный ввод числа в виде %u и вывод числа в виде %10u, где u применяется для беззнакового типа числа, 10 – это количество позиций, отводимое для десятичного числа.

Предполагается, что заданное число может быть представлено 32 разрядами, по 4 группы с 8 разрядами (4 байта по 8 бит в каждом).

Применение оператора сдвига ( ?) выполняется замена 1 на 0 и наоборот.

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

(рис 16.1) Поразрядное инвертирование целого числа

Как видно из рис 16.1, все нули или единицы исходного числа (в двоичном представлении) были инвертированы.

Задание 1

  • Проверьте результат выполнения программы с помощью инженерного калькулятора calc операционной системы Windows.
  • Выполните раздельное объявление переменной mask и раздельное ее определение (инициализацию с поразрядным сдвигом).
  • В теле цикла for вместо функции printf() примените putchar().
  • Дополните программу выводом результатов в файл compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • В качестве стартового числа примите год рождения пользователя.
  • Пример 2. Напишите программу выполнения поразрядной операции И ( ) над двумя целыми числами, представленными в двоичной системе счисления для 32-разрядного компьютера.

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

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    // Главная функция
    int main (void)
     {
    	unsigned int number, mask;
    
    printf("\n The program on demonstration digit-by-digit operation And (  )\n");
    
    	printf("\n\t Enter a whole number of unsigned: ");
    	scanf_s("%u", number);
    
    	printf("\t Enter the number of unsigned-mask: ");
    	scanf_s("%u", mask);
    
    	printf("\n\t Binary representation of the starting number and (%u) and\n", number);
    	printf("\t Binary representation of the number-masks (%u):\n", mask);
    
    	printBits(number);
    	printBits(mask);
    
    // Число после поразрядного умножения (И)
    	printBits(number  mask); 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
     }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) 
    {
    unsigned int b;
    unsigned int mask = 1 << 31; // shift to 31 bit
    
    printf("\n\t %10u = ", var);
    
    for (b = 1; b <= 32; ++b) 
    {
    	printf("%c", var  mask ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

    В программе операция поразрядного умножения – операция И закладывается в фактический параметр функции printBits().

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

    (рис 16.2) Результат поразрядного оператора И над двумя числами

    Как видно из рис 16.2, битовая единица первого числа "проходит" в результат, если в маске на этом же месте (в том же разряде) также находится битовая единица. В результате получается новое число – 32.

    Задание 2

  • В качестве стартового числа примите год рождения пользователя, а в качестве маски – число рождения.
  • Примените операцию поразрядного умножения к введенным отрицательным числам и выведите результат на консоль. Сравните с тем, что выводит функция printBits().
  • Вместо оператора условия ( ?) примените другой способ вывода 1 или 0 после применения поразрядной операции И.
  • Консольный вывод запишите в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 3. Напишите программу демонстрацию поразрядной операции "ИЛИ" (включающей дизъюнкции).

    Операция поразрядного логического ИЛИ ( | ) сравнивает каждый бит первого операнда с соответствующим битом второго операнда. Если любой (или оба) из сравниваемых битов равен 1, то соответствующий бит результата устанавливается в 1, в противном случае результирующий бит равен 0.

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    
    // Главная функция
    int main (void) 
    {
    	unsigned int number1, number2;
    
    printf("\n The program on demonstration digit-by-digit operator OR ( | ):\n");
    
    	printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    
    	printf("\n\t Binary representation of the number of N1 (%u) and\n", number1);
    	printf("\t Binary representation of the number of N2 (%u):\n", number2);
    	printBits(number1);
    	printBits(number2);
    // Число после поразрядного сложения (ИЛИ)
    	printBits(number1 | number2); 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) {
    unsigned int b;
    unsigned int num = 1 << 31; // shift to 31 bit
    printf("\n\t %10u = ", var);
    for (b = 1; b <= 32; ++b) {
    	printf("%c", var  num ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

    В программе использована функция printBits(), как и в предыдущем примере. Она по необходимости преобразует десятичные числа в двоичные. При этом входным параметром функции является поразрядное включающее "ИЛИ" над двумя операндами в главной функции main().

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

    (рис 16.3) Результат поразрядного включающего оператора "ИЛИ"

    Как видно из полученного результата, поразрядная операция ИЛИ представляет собой побитовое сложение чисел при условии, что 1 + 1 = 1.

    Задание 3

  • В качестве исходных чисел примите год и число рождения пользователя.
  • Произведите операцию включающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main().
  • Видоизмените программу для случая, когда имеется 16-разрядный компьютер.
  • Пример 4. Напишите программу демонстрацию поразрядного исключающего ИЛИ ( ^ ).

    Операция поразрядного исключающего ИЛИ ( ^ ) сравнивает каждый бит первого операнда (например, первого числа) с соответствующими битами второго операнда. Если один из сравниваемых битов равен 0, а второй бит равен 1, то соответствующий бит результата устанавливается в 1, в противном случае, т.е. когда оба бита равны 1 или 0, бит результата устанавливается в 0.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    
    int main (void) {
    	unsigned int number1, number2;
    
    printf("\n The program on demonstration digit-by-digit excluding operator OR ( ^ ):\n");
    	printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    
    printf("\n\t Binary representation of the number of N1 (%u) and\n", number1);
    printf("\t Binary representation of the number of N2 (%u):\n", number2);
    
    	printBits(number1);
    	printBits(number2);
    	printBits(number1 ^ number2); // Число после операции ^ 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) {
    unsigned int b;
    unsigned int num = 1 << 31; // shift to 31 bit
    
    printf("\n\t %10u = ", var);
    for (b = 1; b <= 32; ++b) {
    	printf("%c", var  num ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

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

    (рис 16.4) Результат поразрядного исключающего оператора "ИЛИ"

    Задание 4

  • В качестве исходных десятичных чисел примите год и число рождения пользователя.
  • Произведите операцию исключающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main().
  • Подсчитайте число итераций оператора цикла for.
  • Пример 5. Напишите программу выполнения поразрядных операций с шестнадцатеричными числами, которые получаются после преобразования десятичных чисел, введенных с клавиатуры.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    const char basestr[16] = 
    {'0','1','2','3','4','5','6','7','8','9',
    'A','B','C','D','E','F'};
    const char *two16[16] = 
    {"0000","0001","0010","0011","0100","0101","0110","0111",
    "1000","1001", // 0,1,2,3,4,5,6,7,8,9
    "1010","1011","1100","1101","1110","1111"}; //A,B,C,D,E,F
    
    // Прототипы функций
    char *dec2hex(unsigned int var); 
    void printHex2Bin(unsigned int number);
    
    // Главная функция
    int main (void) {
    	int d12;
    unsigned int number1, number2;
    char ch, *NUM1;
    
    printf("\n The program on demonstration digit-by-digit operations\n with hexadecimal numbers\n");
    printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    	_flushall();
    //Для контроля перевода десятичного числа в шестнадцатеричное
    	printf("\n\t Hexadecimal numbers: %X,  %X\n", number1, number2);
    
    	printf("\n\t Enter bit operation ( , |, ^, ~ ): ");
    	scanf_s("%c", ch);
    
    	switch (ch) {
    case '':
    NUM1 = dec2hex(number1  number2);
    printHex2Bin(number1  number2);
    break;
          case '|':
    	NUM1 = dec2hex(number1 | number2);
    printHex2Bin(number1 | number2);
    break;
    case '^':
    NUM1 = dec2hex(number1 ^ number2);
    printHex2Bin(number1 ^ number2);
    break;
          case '~':
    	printf("\t Type 1 - for the first number; type 2 - for the second number: ");
    	scanf_s("%d", d12);
    	_flushall();   
    	if (d12 == 1) { 
    NUM1 = dec2hex(~number1);
    printHex2Bin(~number1); }
    	else {
    NUM1 = dec2hex(~number2);
    printHex2Bin(~number2); }
    break; 
          default :
    fprintf(stdout, "\n\t Unknown symbol.");
    break;
    	}
     	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    char *dec2hex(unsigned int var) {
    int number[64];
    int i, j, k = 0;
    unsigned int base = 16;
    char Hex[128], *PTR;
    PTR = Hex;
    
    do {
    number[k] = var % base;
    ++ k;
    var /= base;
    } while (var != 0);
    
    for (i = k-1, j = 0; i >= 0; --i, ++j) { 
    Hex[j] = (char )basestr[number[i]];
    PTR[j] = Hex[j];
    	}
    PTR[j] = '\0';
    
    	return (PTR);
    } 
    
    void printHex2Bin(unsigned int number) {
    unsigned int num = 1 << 31; 
    unsigned int i;
    
    printf("\n Hexadecimal number: %s\n", dec2hex(number));
    printf(" Binary equivalent:\n ");
    
    for (i = 1; i <= 32; ++i) {
    printf("%2c", (number  num )? '1' : '0');
    number <<= 1; 
    	if (i % 8 == 0)
    		printf("  ");
    	} 
    }

    В программе используется следующий указатель на функцию:

    char *dec2hex(unsigned int var);

    Это дает возможность возвращать указатель с адресом необходимого символьного массива. Обратите внимание на выполнение операции завершения строки в функции *dec2hex() для указателя *PTR.

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

    (рис 16.5) Пример побитовых операций с шестнадцатеричными числами

    Задание 5

  • В программе предусмотрите вывод двоичных эквивалентов для введенных чисел.
  • Измените программу так, чтобы операции с переключателем switch выполнялись в функции printHex2Bin().
  • В функции сформируйте одномерный массив символов двоичного эквивалента заданного шестнадцатеричного числа. Предусмотрите вывод результата.
  • Выполните программу со всеми поразрядными операциями при вводе чисел: года и дня рождения пользователя.
  • Напишите программу перевода десятичных чисел в восьмеричные (вместо *dec2hex() ). Предусмотрите также поразрядные операции с выводом двоичных эквивалентов.
  • В программе предусмотрите копирование консольного содержания в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 6. Напишите программу ротации битов для заданных чисел, представленных в шестнадцатеричном виде, когда биты с самых крайних разрядов не теряются, а переносятся на противоположную сторону [16.2].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <string.h>
    
    const char basestr[16] = 
    {'0','1','2','3','4','5','6','7','8','9',
    'A','B','C','D','E','F'};
    
    int main (void)
     {
    int n;
    unsigned int number, uns;
    char *NUM1, str[128] = "0x", un[] = "u";
    FILE *fid;
    
    // Прототипы функций
    char *dec2hex (unsigned int var); 
    unsigned int rotate (unsigned int value, int n);
    
    printf("\n The program for demonstration of bit-by-bit rotation \n of the numbers set in a hexadecimal kind\n");
    
    printf("\n\t Enter the unsigned decimal: ");
    	scanf_s("%u", number);
    	printf("\t Enter the number of bits of rotation: ");
    	scanf_s("%d", n);
    
    //Для контроля перевода десятичного числа в шестнадцатеричное
    	printf("\n\t Hexadecimal numbers: %X\n", number);
    NUM1 = dec2hex(number);
    strcat_s(str, 127, NUM1);
    strcat_s(str, 127, un);
    
    fopen_s(fid,"temp.txt", "w");
    fprintf(fid,"%s", str);
    fclose(fid);
    
    fopen_s(fid,"temp.txt", "r");
    fscanf_s(fid,"%x", uns);
    fclose(fid);
    
    printf("\n\t After rotating %d-bits: %X\n", n, rotate(uns, n));
     	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }
    //Функция перевода десятичного числа в шестнадцатеричное
    char *dec2hex(unsigned int var) {
    int number[64];
    int i, j, k = 0;
    unsigned int base = 16;
    char Hex[128], *PTR;
    PTR = Hex;
    
    do {
    number[k] = var % base;
    ++ k;
    var /= base;
    } while (var != 0);
    
    for (i = k-1, j = 0; i >= 0; --i, ++j) { 
    Hex[j] = basestr[number[i]];
    PTR[j] = Hex[j];
    	}
    PTR[j] = '\0';
    
    	return (PTR);
    } 
    //Функция ротации числа влево или вправо
    unsigned int rotate (unsigned int var, int n)
     {
    unsigned int result, bits;
    // Ограничение диапазона ротации
    if (n > 0)
    n = n % 32;
    else
    n = -(-n % 32);
    if (n == 0)
    result = var;
    else
    if (n > 0) // Ротация влево
    {
    bits = var >> (32 - n);
    result = var << n | bits;
    }
    else // Ротация вправо
    {
    n = -n;
    bits = var << (32 - n);
    result = var >> n | bits;
    }
    return (result);
    }

    В программе сначала формируется символьное выражение шестнадцатеричного числа с помощью функции dec2hex(). Затем оно приводится к стандартному виду с помощью функции strcat_s(), которая к имеющемуся значению добавляет содержимое строки от функции dec2hex(). После еще добавляется суффикс u типа unsigned int. Для получения собственно шестнадцатеричного числа выполнена операция записи строки в файл и чтения из файла в шестнадцатеричном формате. Далее используется функция rotate() из [16.2]. Ротация переменной на n битов влево производится в соответствии с алгоритмом, состоящим из трех шагов [5]. Сначала извлекаются левые n бит исходного значения в отдельную переменную ( bits ), которая сдвигается вправо (с помощью поразрядного сдвига >> ) на количество позиций ( 32–n ). Затем исходное значение ( var ) сдвигается влево на n битов и наконец над получившимся значением и из влеченными битами производится операция поразрядной дизъюнкции "ИЛИ" ( | ). Аналогичные действия выполняются при сдвиге вправо.

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

    (рис 16.6) Результат ротации заданного числа

    Задание 6

  • Дополните программу выводом на консоль прочитанного числа из текстового файла.
  • В качестве вводимых данных используйте год и месяц рождения пользователя (студента).
  • Выполните ротацию шестнадцатеричных чисел, записанных в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве тестовых чисел и числа битов ротации примите: (0xABCDEF00u, 8), (0xABCDEF00u, –16), (0xFFFF1122u, 4), (0xFFFF1122u, –2), (0xABCDEF00u, 0), (0xABCDEF00u, 44).
  • Напишите программу по ротации восьмеричных чисел.
  • Пример 7. Напишите программу по решению следующего примера. С клавиатуры вводятся два целых числа. Остатки от деления их на 16 заносятся соответственно в 4 младших и 4 старших разряда одного байта. Затем следует напечатать изображение содержимого сформированного байта [16.3].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <local.h>
    
    setlocale(LC_ALL, ".1251");
    
    int main (void)
     {
    int m, n;
    unsigned char k;
    
    //Прототипы функций
    void binar (unsigned char ch);
    unsigned char code16 (int a, int b);
    
    printf("\n\t Введите первое беззнаковое число N1: ");
    scanf_s("%d", m);
    printf("\t Введите второе беззнаковое число N2: ");
    scanf_s("%d", n);
    k = code16(m, n);
    printf("\n\t Код двух остатков старших и младших разрядов байта: %u", k);
    binar(k);
    
    	printf("\n Нажмите любую клавише (Press any key): ");
    	_getch();
    	return 0;
    }
    
    unsigned char code16 (int a, int b){
    //Объединение с вложенной структурой
    union 
    {
    unsigned char z;
    struct 
    {
    	unsigned int x : 4; //Младшие биты
    	unsigned int y : 4; //Старшие биты
    } hh;
    } un;
    un.hh.x = a % 16;
    un.hh.y = b % 16;
    return (un.z);
    }
    
    void binar (unsigned char ch){ 
    int i;
    // Объединение с вложенной структурой
    union {
    unsigned char ss;
    
    // Структура с битовыми полями
    struct {
    	unsigned int a0:1; unsigned int a1:1;
    	unsigned int a2:1; unsigned int a3:1;
    	unsigned int a4:1; unsigned int a5:1;
    	unsigned int a6:1; unsigned int a7:1;
    	} byte;
    } cod;
    
    cod.ss = ch;
    printf("\n\n\t Число разрядов:\n\t");
    
    for (i = 0; i < 8; ++i)
    printf("%4d", 7 - i);
    printf("\n\t Значения битовых полей:\n\t");
    
    printf("%4d%4d%4d%4d%4d%4d%4d%4d",\
    cod.byte.a7, cod.byte.a6, cod.byte.a5, cod.byte.a4,\
    cod.byte.a3, cod.byte.a2, cod.byte.a1, cod.byte.a0);
    printf("\n\n");
    
    }

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

    (рис 16.7) Значения битов остатков от деления двух чисел

    В программе используются две переменных типа объединения и две структурные переменные. Объединение имеет то свойство, что переменные разных типов занимают одну область памяти, соответствующей наибольший размер в байтах. Поэтому если инициализируются одни переменные (например, un.hh.x = a % 16; un.hh.y = b % 16; ), то переменная другого типа ( unsigned char z; ) будет располагаться в той же области памяти, что и переменные типа unsigned int (x и y). В связи с этим якобы неинициализированная переменная un.z возвращается функцией code16(). Размеры полей задаются программистом с учетом того, чтобы в них помещалось соответствующее число, представленное в двоичной системе. Например, если размерность поля равна 2, то в это поле можно записать десятичное число 3, так как его двоичный эквивалент равен 11. В поле с размерностью 8 можно записать число 140, так как его двоичный эквивалент равен 10001100.

    Задание 7

  • В качестве вводимых чисел примите 2*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Выведите на консоль размерность в байтах объединений и структур, определенных в программе.
  • Проанализируйте результат выполнения программы при изменении размера битовых полей в структуре функции code16().
  • Видоизмените программу для ввода трех чисел и определения значений разрядных (битовых) полей после занесения в них остатков от деления на целое число 8.
  • Пример 8. Используя битовые поля структуры, напишите программу вывода на экран дисплея двоичного кода ASCII символа, вводимого с клавиатуры.

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

    Отметим про наборы символов ASCII. Символы сохраняются в памяти компьютеров с использованием числовых кодов. Часто используется кодировка ASCII (American Standard Code for Information Interchange – американский стандартный код для обмена информацией). Таблицу символов ASCII можно посмотреть в [16.1].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    //Шаблон структуры с битовыми полями
    struct byte {
    	int b1 : 1; int b2 : 1;
    	int b3 : 1; int b4 : 1;
    	int b5 : 1; int b6 : 1;
    	int b7 : 1; int b8 : 1; 
    	};
    // Определение объединения с вложенной структурой
    union bits {
    char ch;
    struct byte bit;
    } un;// un - переменная типа объединения
    
    // Прототип функции
    void decode (union bits bt, int ch);
    
    // Главная функция
    int main (void) {
    	printf("\n\t Enter any symbol or Ctrl+Z to quit:\n");
    	do 	{
    		printf("\n\t Enter: ");
    		un.ch = getchar();
    	if ( (un.ch) == EOF) break;
    decode(un, un.ch);	
    	} while ((un.ch = getchar())!= EOF);
    
    	printf("\n Press any key: ");
    	_getch();
    	return 0; }
    // Функция двоичного представления символов
    void decode (union bits bt, int ch) {
    printf("\tBinary code of '%c':\n", ch);
    if (bt.bit.b8) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    
    if (bt.bit.b7) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    
    if (bt.bit.b6) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b5) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b4) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b3) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b2) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b1) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    printf("\n");
    
    }

    Программа ориентирована на 8 бит одного байта целочисленного значения, которым кодируется символ, вводимый с клавиатуры. В функции decode() использовано форматирование на основе символа "пробел".

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

    (рис 16.8) Пример вывода двоичных кодов заданных символов

    Задание 8

  • Вместо цикла do–while примените другой оператор цикла.
  • Напишите программу вывода всех строчных букв латинского алфавита и их двоичных эквивалентов в кодировке ASCII без ввода их с клавиатуры.
  • Пример 9. Напишите программу левого поразрядного сдвига для вводимого с клавиатуры целого числа с выводом его двоичного эквивалента и с повторными сдвигами влево.

    В программе решения примера следует предусмотреть перевод числа из десятичной системы счисления в двоичную систему счисления. При поразрядном сдвиге влево на освободившееся место (места) двоичного числа записываются нули.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Главная функция
    int main (void)
     {
    	long int a;
    	unsigned int m, n;
    //Прототип функции
    void dec2(long int var, unsigned int m, unsigned int n);
    	
    	printf("\n\t Enter an integer: ");
    		scanf_s("%ld", a);
    		_flushall();
    		
    	printf("\t Enter a value shift: ");
    		scanf_s("%u", m);
    		_flushall();
    	printf("\t Enter number of repeated shifts: ");
    		scanf_s("%u", n);
    		_flushall();
    dec2(a, m, n);
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }
    
    // Функция поразрядного сдвига
    void dec2(long int var, unsigned int m, unsigned int n) {
    unsigned int b, i;
    long int mask = 1 << 31; 
    long int M[128];
    
    for (i = 0; i < n; ++i) 	
    M[i] = var << i*m;
    printf("\n\t Decimal and its binary equivalent after the shift:\n");
    
    for (i = 0; i < n; ++i)  {	
    	if (i == 0) {
    	printf("\n Initial number %ld:\n\t", M[0]);
    for (b = 1; b <= 32; ++b)  {
    	printf("%c", M[0]  mask ? '1' : '0');
    	M[0] <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' '); }
    	} 
    else {
    		printf("\n The following number %ld:\n\t", M[i]);
    	for (b = 1; b <= 32; ++b) {
    	printf("%c", M[i]  mask ? '1' : '0');
    	M[i] <<= 1; 
    	if (b % 8 == 0)
    		putchar(' '); }
    }
    	printf("\n"); }
    }

    В программе предполагалось, что в наличии 32-разрядный компьютер. Кроме того, вывод двоичного эквивалента сделан побайтно, считая, что в одном байте находится 8 бит.

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

    (рис 16.9) Поразрядный сдвиг влево заданного числа

    Задание 9

  • Проверьте результат выполнения программы с помощью инженерного калькулятора (calc).
  • В программу добавьте нумерацию результатов вывода десятичного числа и его двоичного эквивалента.
  • Введите число своего дня рождения пользователя.
  • Проверьте программу при вводе отрицательных целых чисел.
  • Измените программу для поразрядного сдвига вправо.
  • Контрольные вопросы

  • Как осуществляется нумерация разрядов байта?
  • Для каких систем счисления в языке С имеются классификаторы форматируемых данных?
  • Какие логические поразрядные операции существуют в языке С?
  • Какие логические операции сдвига существуют в языке С? Какими операторами они реализуются?
  • Что такое битовое поле в языке С? Где оно может быть определено?
  • В чем отличие поразрядных и логических операторов НЕ, И и ИЛИ?
  • Как можно обменять значения двух целочисленных переменных без использования третьей переменной?
  • Чем отличается операция сдвига вправо для типов int и unsigned?
  • Страницы:

    Теоретическая часть

    16.1. Базовые системы счисления

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

    У бита возможны только два значения: 0 и 1. Техническая реализация таких состояний легко реализуется, например, включено–выключено, положительное значение напряжения–отрицательное значение напряжения (определенного уровня) и т.д.

    В языке программирования С термин байт используется для обозначения размера (разрядности) хранения набора символов. Поэтому в языке С байт может содержать 8, 9, 16 и другое количество разрядов. Однако в характеристиках модулей памяти и систем передачи данных предполагается, что байт содержит восемь разрядов [16.1].

    Разряды байта пронумерованы справа налево числами от 0 до 7. Седьмой разряд (крайний левый) называется старшим, а нулевой разряд (крайний правый) – младшим.

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

    128 64 32 16 8 4 2 1 = 255
    27 26 25 24 23 22 21 20 = 255

    Наименьшему значению соответствует комбинация нулей 00000000, которая представляет собой просто нуль [9].

    Байт может хранить числа от 0 до 255, что составляет 256 возможных значений. Программа может интерпретировать комбинацию разрядов иначе и применять байт для хранения чисел от –128 до 127, что составляет 256 возможных значений. Например, тип unsigned char обычно характеризуется использованием байта для представления чисел в диапазоне от 0 до 255, тип signed char – до 127.

    В основании восьмеричной системы счисления лежит число 8 (23). Каждое знакоместо восьмеричного числа соответствует определенной степени восьми. Для записи используются цифры от 0 до 7. Каждая цифра восьмеричного числа соответствует трем двоичным цифрам. Двоичные эквиваленты восьмеричных цифр представлены в табл. 16.1.

    Двоичные эквиваленты восьмеричных цифр
    Восьмеричная цифра Двоичный эквивалент
    0 000
    1 001
    2 010
    3 011
    4 100
    5 101
    6 110
    7 111

    В шестнадцатеричной системе счисления используются степени числа 16 и цифры от 0 до 15. Для представления цифр, соответствующих десятичным значениям от 10 до 15, используются буквы от A до F [16.1]. Например, шестнадцатеричное число A3F (в языке С записывается как 0xA3F ) представляет значение

    $$10\times16^2 + 3\times16^1 + 15\times16^0 = 2 623$$ (по основанию 10).

    В приведенной записи числу 10 соответствует А, а числу 15 – F.

    Язык С допускает использование букв нижнего и верхнего регистра (строчные и прописные буквы) для обозначения шестнадцатеричных цифр. Таким образом, число 2 623 в шестнадцатеричной системе счисления можно записать как 0xA3F, так и 0xa3f.

    Каждая цифра шестнадцатеричного числа соответствует 4-значному двоичному числу [16.1]. Поэтому две шестнадцатеричных цифры соответствуют одному восьмиразрядному байту. Первая цифра представляет 4 старших разряда, а вторая цифра – 4 младших разряда.

    Соответствие между шестнадцатеричными цифрами, десятичными и двоичными числами показано в табл. 16.2.

    Эквиваленты шестнадцатеричных чисел
    Десятичное число Шестнадцатеричная цифра Двоичный эквивалент
    1 2 3
    0 0 0000
    1 1 0001
    2 2 0010
    3 3 0011
    4 4 0100
    5 5 0101
    6 6 0110
    7 7 0111
    8 8 1000
    9 9 1001
    10 A 1010
    11 B 1011
    12 C 1100
    13 D 1101
    14 E 1110
    15 F 1111

    В языке С существуют два средства управления разрядами [16.1]. Первое представляет собой набор поразрядных операций, а второе – форму полей данных, которое предоставляет доступ к разрядам значения типа int.

    Далее будут использоваться 8-разрядные числа в двоичной системе счисления.

    16.2. Логические поразрядные операции

    В языке программирования С существуют два вида поразрядных операций: логические операции и операции сдвига [16.1].

    Поразрядные операции выполняются над каждым разрядом независимо от разрядов слева или справа. Поразрядные операции выполняются над целыми числами. Рассмотрим логические поразрядные операции.

    Унарная операция $$\sim$$ преобразовывает все единицы в нули и все нули в единицы (предполагается, что операции производятся над двоичными числами). Данную операцию называют также операцией "дополнение", т.е. когда все биты, равные 0, устанавливаются в 1, а когда все биты, равные 1, – устанавливаются в 0.

    Поразрядная операция И обозначается символом .

    Двоичная операция создает новое значение за счет выполнения поразрядного сравнения двух операндов. Для каждой позиции результирующий разряд будет иметь значение 1 только в случае, когда соответствующие разряды обоих операндов имеют значение 1. Можно сказать, что когда над двумя значениями производится операция поразрядного умножения , то двоичные представления чисел сравниваются бит за битом [2]. Например, пусть одна переменная w1 есть число 25, а другая w2 – число 77. Соответственно в двоичном представлении 25 = 0000000000011001, 77 = 0000000001001101. Тогда в результате поразрядной операции получим число w3 = w1 w2. Результат действия оператора можно представить следующим образом:

    Поразрядная операция И ()
    w1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 (25)
    w2 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 (77)
    w3 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 (9)

    В результате поразрядной операции "" над двумя числами (25 и 77 в двоичном представлении), которые имеют, например, 16 бит, получили новое число, т.е. 9.

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

    w3 = w1  3;

    переменной w3 присваивается значение переменной w1, для которой выполнена поразрядная операция со значением 3. При этом все биты левее двух младших битов устанавливаются в 0, а остальные биты сохраняют свое значение [16.2]. Маска представляет собой некоторую комбинацию разрядов [16.1]. Разряды маски с нулевыми значениями можно считать аналогом непрозрачных ячеек реальной маски, а разряды со значениями 1 – прозрачными ячейками.

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

    Поразрядная операция ИЛИ обозначается символом |.

    Когда над двумя значениями производится операция поразрядно ИЛИ, то последовательно сравниваются значения всех битов при двоичном представлении этих значений [5]. Если при этом соответствующий бит имеет значение 1 в первом или втором операнде, то результирующее значение будет равно 1. Рассмотрим предыдущий пример с поразрядной операции ИЛИ:

    Поразрядная операция ИЛИ (|)
    w1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 (25)
    w2 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 (77)
    w3 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 1 (93)

    Поразрядную операцию ИЛИ (|) обычно используют для установки заданных битов слова в 1. Логическую операцию ИЛИ называют также включающей дизъюнкцией, или логическим сложением. Часто применяется обозначение OR.

    Поразрядная операция исключающего ИЛИ ( ^ ) работает следующим образом [5]. Сравниваются соответствующие биты двух операндов, и если только один из битов равен 1, то результат будет равен 1. А при равенстве обоих соответствующих битов или 0, или 1 результат будет равен 0. Для двух операндов b1, b2 при использовании исключающего ИЛИ ( ^ ) справедлива таблица истинности (табл. 16.3).

    Таблица истинности операции исключающего ИЛИ ( ^ )
    b1 b2 b1 ^ b2
    0 0 0
    0 1 1
    1 0 1
    1 1 0

    Если операцию исключающего ИЛИ ( ^ ) использовать для одного и того же значения, то в результате будет получено нулевое значение. Этот прием часто использовался программистами на языке ассемблера как наиболее быстрый путь установить значение в нуль или сравнить два значения на их равенство. Этот способ не рекомендуется использовать в языке программирования С, так как при этом скорость работы не повышается, а программа становится менее понятной [16.2].

    Операция исключающего ИЛИ ( ^ ) может применяться для перестановки значений двух переменных без выделения дополнительной памяти (и, соответственно, без использования дополнительной переменной).

    Поразрядная операция исключающего "ИЛИ" называется также исключающей дизъюнкцией. Часто применяется обозначение XOR.

    1.3. Поразрядные операции сдвига

    Оператор сдвига влево: <<

    Когда оператор сдвига влево ( << ) выполняется над некоторым значением, все биты, составляющие это значение, сдвигаются влево [16.2]. Связанное с этим оператором число показывает количество бит, на которое значение должно переместиться. Биты, которые сдвигаются со старшего разряда, считаются потерянными, а на место младших битов всегда помещаются нули.

    Оператор сдвига вправо: >>

    Операция сдвига вправо ( >> ) сдвигает разряды левого операнда вправо на количество позиций, указываемое правым операндом. Выходящие за правую границу разряды теряются. Для типов данных без знака ( unsigned ) освобождаемые слева позиции заполняются нулями. Для знаковых типов данных результат зависит от используемой системы. Освобождаемые позиции могут заполняться нулями либо копиями знакового (первого слева) разряда [16.1].

    Поразрядные операции сдвига могут служить удобным и эффективным (в зависимости) средством выполнения операций умножения и деления на числа, представляющие собой степени двойки [16.1]. Такие операции аналогичны смещению десятичной точки при умножении или делении на 10.

    1.4. Битовые поля

    Второй метод управления разрядами состоит в использовании битового (разрядного) поля [9], которое представляет собой просто последовательную цепочку разрядов в рамках значения типа signed int или unsigned int.

    Битовое поле может быть только элементом структуры или объединения и вне объектов этих типов не встречается [16.3].

    Битовое поле создается путем объявления структуры (объединения), которая помечает каждое поле и определяет его разряд.

    Приведем пример из [16.2] с использованием битовых полей в структуре:

    struct packed_struct {
    unsigned int  : 3;
    unsigned int f1 : 1;
    unsigned int f2 : 1;
    unsigned int f3 : 1;
    unsigned int type : 8;
    unsigned int index : 18;
    };

    В созданном шаблоне-структуре с дескриптором (именем-этикеткой) packed_struct первый член не имеет имени. Символ , ":3" задает три безымянных бита. Второй, третий и четвертый члены структуры, f1, f2, f3, также имеют тип unsigned int. Символ ":1" говорит о том, что в данном члене структуры будет храниться 1 бит. Член структуры с именем type в памяти занимает 8 бит. Член структуры index рассчитан на хранение 18 бит.

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

    struct packed_struct packed_data;

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

    packed_data.type = 7;

    Если ранее была объявлена какая-то переменная, например, n, то присвоение может быть таким:

    packed_data.type = n;

    При этом нет необходимости беспокоиться о том, что значение переменной n будет слишком большим. Только младшие 8 бит будут учитываться при присваивании значения для поля packed_data.type.

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

    n = packed_data.type;

    В приведенном примере после извлечения значения поля type будет произведен сдвиг в сторону младших бит.

    Битовые поля могут быть объявлены только как тип int (в стандарте С99 также _Bool ). Если битовое поле имеет тип int, то этот знаковый ( signed ) или беззнаковый ( unsigned ) тип зависит от реализации.

    Для исключения неоднозначности следует использовать явные объявления: signed int или unsigned int.

    Битовые поля нельзя объединять в массивы. Нельзя использовать адрес битового поля, поэтому не может быть такого типа, как "указатель на битовое поле" [16.2]. Компилятор языка программирования С не переупорядочивает битовые поля для получения оптимального распределения памяти. Но в некоторых случаях может производиться выравнивание за счет безымянного поля. Это может использоваться для выравнивания следующего поля структуры по границе блока [16.2].

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

    Практическая часть

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

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

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    
    // Главная функция
    int main (void) {
    	unsigned int number;
    
    printf("\n The program on demonstration digit-by-digit operation of denying ( ~ )\n");
    
    	printf("\n\t Enter a whole number of unsigned: ");
    	scanf_s("%d", number);
    	printf("\n\t Binary representation of the starting number and\n");
    	printf("\t Binary representation of bitwise negation of the initial number:\n");
    	printBits(number); //Исходное число
    printBits(~number); // Число после поразрядного дополнения
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var)
    {
    unsigned int b;
    unsigned int mask = 1 << 31; // shift to 31 bit
    printf("\n\t %10u = ", var);
    for (b = 1; b <= 32; ++b) {
    	printf("%c", var  mask ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    }
    }

    В программе применен форматный ввод числа в виде %u и вывод числа в виде %10u, где u применяется для беззнакового типа числа, 10 – это количество позиций, отводимое для десятичного числа.

    Предполагается, что заданное число может быть представлено 32 разрядами, по 4 группы с 8 разрядами (4 байта по 8 бит в каждом).

    Применение оператора сдвига ( ?) выполняется замена 1 на 0 и наоборот.

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

    (рис 16.1) Поразрядное инвертирование целого числа

    Как видно из рис 16.1, все нули или единицы исходного числа (в двоичном представлении) были инвертированы.

    Задание 1

  • Проверьте результат выполнения программы с помощью инженерного калькулятора calc операционной системы Windows.
  • Выполните раздельное объявление переменной mask и раздельное ее определение (инициализацию с поразрядным сдвигом).
  • В теле цикла for вместо функции printf() примените putchar().
  • Дополните программу выводом результатов в файл compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • В качестве стартового числа примите год рождения пользователя.
  • Пример 2. Напишите программу выполнения поразрядной операции И ( ) над двумя целыми числами, представленными в двоичной системе счисления для 32-разрядного компьютера.

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

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    // Главная функция
    int main (void)
     {
    	unsigned int number, mask;
    
    printf("\n The program on demonstration digit-by-digit operation And (  )\n");
    
    	printf("\n\t Enter a whole number of unsigned: ");
    	scanf_s("%u", number);
    
    	printf("\t Enter the number of unsigned-mask: ");
    	scanf_s("%u", mask);
    
    	printf("\n\t Binary representation of the starting number and (%u) and\n", number);
    	printf("\t Binary representation of the number-masks (%u):\n", mask);
    
    	printBits(number);
    	printBits(mask);
    
    // Число после поразрядного умножения (И)
    	printBits(number  mask); 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
     }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) 
    {
    unsigned int b;
    unsigned int mask = 1 << 31; // shift to 31 bit
    
    printf("\n\t %10u = ", var);
    
    for (b = 1; b <= 32; ++b) 
    {
    	printf("%c", var  mask ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

    В программе операция поразрядного умножения – операция И закладывается в фактический параметр функции printBits().

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

    (рис 16.2) Результат поразрядного оператора И над двумя числами

    Как видно из рис 16.2, битовая единица первого числа "проходит" в результат, если в маске на этом же месте (в том же разряде) также находится битовая единица. В результате получается новое число – 32.

    Задание 2

  • В качестве стартового числа примите год рождения пользователя, а в качестве маски – число рождения.
  • Примените операцию поразрядного умножения к введенным отрицательным числам и выведите результат на консоль. Сравните с тем, что выводит функция printBits().
  • Вместо оператора условия ( ?) примените другой способ вывода 1 или 0 после применения поразрядной операции И.
  • Консольный вывод запишите в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 3. Напишите программу демонстрацию поразрядной операции "ИЛИ" (включающей дизъюнкции).

    Операция поразрядного логического ИЛИ ( | ) сравнивает каждый бит первого операнда с соответствующим битом второго операнда. Если любой (или оба) из сравниваемых битов равен 1, то соответствующий бит результата устанавливается в 1, в противном случае результирующий бит равен 0.

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    
    // Главная функция
    int main (void) 
    {
    	unsigned int number1, number2;
    
    printf("\n The program on demonstration digit-by-digit operator OR ( | ):\n");
    
    	printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    
    	printf("\n\t Binary representation of the number of N1 (%u) and\n", number1);
    	printf("\t Binary representation of the number of N2 (%u):\n", number2);
    	printBits(number1);
    	printBits(number2);
    // Число после поразрядного сложения (ИЛИ)
    	printBits(number1 | number2); 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) {
    unsigned int b;
    unsigned int num = 1 << 31; // shift to 31 bit
    printf("\n\t %10u = ", var);
    for (b = 1; b <= 32; ++b) {
    	printf("%c", var  num ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

    В программе использована функция printBits(), как и в предыдущем примере. Она по необходимости преобразует десятичные числа в двоичные. При этом входным параметром функции является поразрядное включающее "ИЛИ" над двумя операндами в главной функции main().

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

    (рис 16.3) Результат поразрядного включающего оператора "ИЛИ"

    Как видно из полученного результата, поразрядная операция ИЛИ представляет собой побитовое сложение чисел при условии, что 1 + 1 = 1.

    Задание 3

  • В качестве исходных чисел примите год и число рождения пользователя.
  • Произведите операцию включающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main().
  • Видоизмените программу для случая, когда имеется 16-разрядный компьютер.
  • Пример 4. Напишите программу демонстрацию поразрядного исключающего ИЛИ ( ^ ).

    Операция поразрядного исключающего ИЛИ ( ^ ) сравнивает каждый бит первого операнда (например, первого числа) с соответствующими битами второго операнда. Если один из сравниваемых битов равен 0, а второй бит равен 1, то соответствующий бит результата устанавливается в 1, в противном случае, т.е. когда оба бита равны 1 или 0, бит результата устанавливается в 0.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Прототип функции
    void printBits(unsigned int var);
    
    int main (void) {
    	unsigned int number1, number2;
    
    printf("\n The program on demonstration digit-by-digit excluding operator OR ( ^ ):\n");
    	printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    
    printf("\n\t Binary representation of the number of N1 (%u) and\n", number1);
    printf("\t Binary representation of the number of N2 (%u):\n", number2);
    
    	printBits(number1);
    	printBits(number2);
    	printBits(number1 ^ number2); // Число после операции ^ 
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    // Функция побитового представления целого числа без знака
    void printBits(unsigned int var) {
    unsigned int b;
    unsigned int num = 1 << 31; // shift to 31 bit
    
    printf("\n\t %10u = ", var);
    for (b = 1; b <= 32; ++b) {
    	printf("%c", var  num ? '1' : '0');
    	var <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' ');
    	} // End 2nd for
    } // End function

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

    (рис 16.4) Результат поразрядного исключающего оператора "ИЛИ"

    Задание 4

  • В качестве исходных десятичных чисел примите год и число рождения пользователя.
  • Произведите операцию исключающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main().
  • Подсчитайте число итераций оператора цикла for.
  • Пример 5. Напишите программу выполнения поразрядных операций с шестнадцатеричными числами, которые получаются после преобразования десятичных чисел, введенных с клавиатуры.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    const char basestr[16] = 
    {'0','1','2','3','4','5','6','7','8','9',
    'A','B','C','D','E','F'};
    const char *two16[16] = 
    {"0000","0001","0010","0011","0100","0101","0110","0111",
    "1000","1001", // 0,1,2,3,4,5,6,7,8,9
    "1010","1011","1100","1101","1110","1111"}; //A,B,C,D,E,F
    
    // Прототипы функций
    char *dec2hex(unsigned int var); 
    void printHex2Bin(unsigned int number);
    
    // Главная функция
    int main (void) {
    	int d12;
    unsigned int number1, number2;
    char ch, *NUM1;
    
    printf("\n The program on demonstration digit-by-digit operations\n with hexadecimal numbers\n");
    printf("\n\t Enter the unsigned number of N1: ");
    	scanf_s("%u", number1);
    	printf("\t Enter the unsigned number of N2: ");
    	scanf_s("%u", number2);
    	_flushall();
    //Для контроля перевода десятичного числа в шестнадцатеричное
    	printf("\n\t Hexadecimal numbers: %X,  %X\n", number1, number2);
    
    	printf("\n\t Enter bit operation ( , |, ^, ~ ): ");
    	scanf_s("%c", ch);
    
    	switch (ch) {
    case '':
    NUM1 = dec2hex(number1  number2);
    printHex2Bin(number1  number2);
    break;
          case '|':
    	NUM1 = dec2hex(number1 | number2);
    printHex2Bin(number1 | number2);
    break;
    case '^':
    NUM1 = dec2hex(number1 ^ number2);
    printHex2Bin(number1 ^ number2);
    break;
          case '~':
    	printf("\t Type 1 - for the first number; type 2 - for the second number: ");
    	scanf_s("%d", d12);
    	_flushall();   
    	if (d12 == 1) { 
    NUM1 = dec2hex(~number1);
    printHex2Bin(~number1); }
    	else {
    NUM1 = dec2hex(~number2);
    printHex2Bin(~number2); }
    break; 
          default :
    fprintf(stdout, "\n\t Unknown symbol.");
    break;
    	}
     	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }
    
    char *dec2hex(unsigned int var) {
    int number[64];
    int i, j, k = 0;
    unsigned int base = 16;
    char Hex[128], *PTR;
    PTR = Hex;
    
    do {
    number[k] = var % base;
    ++ k;
    var /= base;
    } while (var != 0);
    
    for (i = k-1, j = 0; i >= 0; --i, ++j) { 
    Hex[j] = (char )basestr[number[i]];
    PTR[j] = Hex[j];
    	}
    PTR[j] = '\0';
    
    	return (PTR);
    } 
    
    void printHex2Bin(unsigned int number) {
    unsigned int num = 1 << 31; 
    unsigned int i;
    
    printf("\n Hexadecimal number: %s\n", dec2hex(number));
    printf(" Binary equivalent:\n ");
    
    for (i = 1; i <= 32; ++i) {
    printf("%2c", (number  num )? '1' : '0');
    number <<= 1; 
    	if (i % 8 == 0)
    		printf("  ");
    	} 
    }

    В программе используется следующий указатель на функцию:

    char *dec2hex(unsigned int var);

    Это дает возможность возвращать указатель с адресом необходимого символьного массива. Обратите внимание на выполнение операции завершения строки в функции *dec2hex() для указателя *PTR.

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

    (рис 16.5) Пример побитовых операций с шестнадцатеричными числами

    Задание 5

  • В программе предусмотрите вывод двоичных эквивалентов для введенных чисел.
  • Измените программу так, чтобы операции с переключателем switch выполнялись в функции printHex2Bin().
  • В функции сформируйте одномерный массив символов двоичного эквивалента заданного шестнадцатеричного числа. Предусмотрите вывод результата.
  • Выполните программу со всеми поразрядными операциями при вводе чисел: года и дня рождения пользователя.
  • Напишите программу перевода десятичных чисел в восьмеричные (вместо *dec2hex() ). Предусмотрите также поразрядные операции с выводом двоичных эквивалентов.
  • В программе предусмотрите копирование консольного содержания в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 6. Напишите программу ротации битов для заданных чисел, представленных в шестнадцатеричном виде, когда биты с самых крайних разрядов не теряются, а переносятся на противоположную сторону [16.2].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <string.h>
    
    const char basestr[16] = 
    {'0','1','2','3','4','5','6','7','8','9',
    'A','B','C','D','E','F'};
    
    int main (void)
     {
    int n;
    unsigned int number, uns;
    char *NUM1, str[128] = "0x", un[] = "u";
    FILE *fid;
    
    // Прототипы функций
    char *dec2hex (unsigned int var); 
    unsigned int rotate (unsigned int value, int n);
    
    printf("\n The program for demonstration of bit-by-bit rotation \n of the numbers set in a hexadecimal kind\n");
    
    printf("\n\t Enter the unsigned decimal: ");
    	scanf_s("%u", number);
    	printf("\t Enter the number of bits of rotation: ");
    	scanf_s("%d", n);
    
    //Для контроля перевода десятичного числа в шестнадцатеричное
    	printf("\n\t Hexadecimal numbers: %X\n", number);
    NUM1 = dec2hex(number);
    strcat_s(str, 127, NUM1);
    strcat_s(str, 127, un);
    
    fopen_s(fid,"temp.txt", "w");
    fprintf(fid,"%s", str);
    fclose(fid);
    
    fopen_s(fid,"temp.txt", "r");
    fscanf_s(fid,"%x", uns);
    fclose(fid);
    
    printf("\n\t After rotating %d-bits: %X\n", n, rotate(uns, n));
     	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }
    //Функция перевода десятичного числа в шестнадцатеричное
    char *dec2hex(unsigned int var) {
    int number[64];
    int i, j, k = 0;
    unsigned int base = 16;
    char Hex[128], *PTR;
    PTR = Hex;
    
    do {
    number[k] = var % base;
    ++ k;
    var /= base;
    } while (var != 0);
    
    for (i = k-1, j = 0; i >= 0; --i, ++j) { 
    Hex[j] = basestr[number[i]];
    PTR[j] = Hex[j];
    	}
    PTR[j] = '\0';
    
    	return (PTR);
    } 
    //Функция ротации числа влево или вправо
    unsigned int rotate (unsigned int var, int n)
     {
    unsigned int result, bits;
    // Ограничение диапазона ротации
    if (n > 0)
    n = n % 32;
    else
    n = -(-n % 32);
    if (n == 0)
    result = var;
    else
    if (n > 0) // Ротация влево
    {
    bits = var >> (32 - n);
    result = var << n | bits;
    }
    else // Ротация вправо
    {
    n = -n;
    bits = var << (32 - n);
    result = var >> n | bits;
    }
    return (result);
    }

    В программе сначала формируется символьное выражение шестнадцатеричного числа с помощью функции dec2hex(). Затем оно приводится к стандартному виду с помощью функции strcat_s(), которая к имеющемуся значению добавляет содержимое строки от функции dec2hex(). После еще добавляется суффикс u типа unsigned int. Для получения собственно шестнадцатеричного числа выполнена операция записи строки в файл и чтения из файла в шестнадцатеричном формате. Далее используется функция rotate() из [16.2]. Ротация переменной на n битов влево производится в соответствии с алгоритмом, состоящим из трех шагов [5]. Сначала извлекаются левые n бит исходного значения в отдельную переменную ( bits ), которая сдвигается вправо (с помощью поразрядного сдвига >> ) на количество позиций ( 32–n ). Затем исходное значение ( var ) сдвигается влево на n битов и наконец над получившимся значением и из влеченными битами производится операция поразрядной дизъюнкции "ИЛИ" ( | ). Аналогичные действия выполняются при сдвиге вправо.

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

    (рис 16.6) Результат ротации заданного числа

    Задание 6

  • Дополните программу выводом на консоль прочитанного числа из текстового файла.
  • В качестве вводимых данных используйте год и месяц рождения пользователя (студента).
  • Выполните ротацию шестнадцатеричных чисел, записанных в текстовый файл с именем compX.txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве тестовых чисел и числа битов ротации примите: (0xABCDEF00u, 8), (0xABCDEF00u, –16), (0xFFFF1122u, 4), (0xFFFF1122u, –2), (0xABCDEF00u, 0), (0xABCDEF00u, 44).
  • Напишите программу по ротации восьмеричных чисел.
  • Пример 7. Напишите программу по решению следующего примера. С клавиатуры вводятся два целых числа. Остатки от деления их на 16 заносятся соответственно в 4 младших и 4 старших разряда одного байта. Затем следует напечатать изображение содержимого сформированного байта [16.3].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <local.h>
    
    setlocale(LC_ALL, ".1251");
    
    int main (void)
     {
    int m, n;
    unsigned char k;
    
    //Прототипы функций
    void binar (unsigned char ch);
    unsigned char code16 (int a, int b);
    
    printf("\n\t Введите первое беззнаковое число N1: ");
    scanf_s("%d", m);
    printf("\t Введите второе беззнаковое число N2: ");
    scanf_s("%d", n);
    k = code16(m, n);
    printf("\n\t Код двух остатков старших и младших разрядов байта: %u", k);
    binar(k);
    
    	printf("\n Нажмите любую клавише (Press any key): ");
    	_getch();
    	return 0;
    }
    
    unsigned char code16 (int a, int b){
    //Объединение с вложенной структурой
    union 
    {
    unsigned char z;
    struct 
    {
    	unsigned int x : 4; //Младшие биты
    	unsigned int y : 4; //Старшие биты
    } hh;
    } un;
    un.hh.x = a % 16;
    un.hh.y = b % 16;
    return (un.z);
    }
    
    void binar (unsigned char ch){ 
    int i;
    // Объединение с вложенной структурой
    union {
    unsigned char ss;
    
    // Структура с битовыми полями
    struct {
    	unsigned int a0:1; unsigned int a1:1;
    	unsigned int a2:1; unsigned int a3:1;
    	unsigned int a4:1; unsigned int a5:1;
    	unsigned int a6:1; unsigned int a7:1;
    	} byte;
    } cod;
    
    cod.ss = ch;
    printf("\n\n\t Число разрядов:\n\t");
    
    for (i = 0; i < 8; ++i)
    printf("%4d", 7 - i);
    printf("\n\t Значения битовых полей:\n\t");
    
    printf("%4d%4d%4d%4d%4d%4d%4d%4d",\
    cod.byte.a7, cod.byte.a6, cod.byte.a5, cod.byte.a4,\
    cod.byte.a3, cod.byte.a2, cod.byte.a1, cod.byte.a0);
    printf("\n\n");
    
    }

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

    (рис 16.7) Значения битов остатков от деления двух чисел

    В программе используются две переменных типа объединения и две структурные переменные. Объединение имеет то свойство, что переменные разных типов занимают одну область памяти, соответствующей наибольший размер в байтах. Поэтому если инициализируются одни переменные (например, un.hh.x = a % 16; un.hh.y = b % 16; ), то переменная другого типа ( unsigned char z; ) будет располагаться в той же области памяти, что и переменные типа unsigned int (x и y). В связи с этим якобы неинициализированная переменная un.z возвращается функцией code16(). Размеры полей задаются программистом с учетом того, чтобы в них помещалось соответствующее число, представленное в двоичной системе. Например, если размерность поля равна 2, то в это поле можно записать десятичное число 3, так как его двоичный эквивалент равен 11. В поле с размерностью 8 можно записать число 140, так как его двоичный эквивалент равен 10001100.

    Задание 7

  • В качестве вводимых чисел примите 2*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Выведите на консоль размерность в байтах объединений и структур, определенных в программе.
  • Проанализируйте результат выполнения программы при изменении размера битовых полей в структуре функции code16().
  • Видоизмените программу для ввода трех чисел и определения значений разрядных (битовых) полей после занесения в них остатков от деления на целое число 8.
  • Пример 8. Используя битовые поля структуры, напишите программу вывода на экран дисплея двоичного кода ASCII символа, вводимого с клавиатуры.

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

    Отметим про наборы символов ASCII. Символы сохраняются в памяти компьютеров с использованием числовых кодов. Часто используется кодировка ASCII (American Standard Code for Information Interchange – американский стандартный код для обмена информацией). Таблицу символов ASCII можно посмотреть в [16.1].

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    //Шаблон структуры с битовыми полями
    struct byte {
    	int b1 : 1; int b2 : 1;
    	int b3 : 1; int b4 : 1;
    	int b5 : 1; int b6 : 1;
    	int b7 : 1; int b8 : 1; 
    	};
    // Определение объединения с вложенной структурой
    union bits {
    char ch;
    struct byte bit;
    } un;// un - переменная типа объединения
    
    // Прототип функции
    void decode (union bits bt, int ch);
    
    // Главная функция
    int main (void) {
    	printf("\n\t Enter any symbol or Ctrl+Z to quit:\n");
    	do 	{
    		printf("\n\t Enter: ");
    		un.ch = getchar();
    	if ( (un.ch) == EOF) break;
    decode(un, un.ch);	
    	} while ((un.ch = getchar())!= EOF);
    
    	printf("\n Press any key: ");
    	_getch();
    	return 0; }
    // Функция двоичного представления символов
    void decode (union bits bt, int ch) {
    printf("\tBinary code of '%c':\n", ch);
    if (bt.bit.b8) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    
    if (bt.bit.b7) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    
    if (bt.bit.b6) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b5) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b4) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b3) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b2) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    if (bt.bit.b1) printf("%2c 1", ' ');
    else printf("%2c 0", ' ');
    printf("\n");
    
    }

    Программа ориентирована на 8 бит одного байта целочисленного значения, которым кодируется символ, вводимый с клавиатуры. В функции decode() использовано форматирование на основе символа "пробел".

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

    (рис 16.8) Пример вывода двоичных кодов заданных символов

    Задание 8

  • Вместо цикла do–while примените другой оператор цикла.
  • Напишите программу вывода всех строчных букв латинского алфавита и их двоичных эквивалентов в кодировке ASCII без ввода их с клавиатуры.
  • Пример 9. Напишите программу левого поразрядного сдвига для вводимого с клавиатуры целого числа с выводом его двоичного эквивалента и с повторными сдвигами влево.

    В программе решения примера следует предусмотреть перевод числа из десятичной системы счисления в двоичную систему счисления. При поразрядном сдвиге влево на освободившееся место (места) двоичного числа записываются нули.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    // Главная функция
    int main (void)
     {
    	long int a;
    	unsigned int m, n;
    //Прототип функции
    void dec2(long int var, unsigned int m, unsigned int n);
    	
    	printf("\n\t Enter an integer: ");
    		scanf_s("%ld", a);
    		_flushall();
    		
    	printf("\t Enter a value shift: ");
    		scanf_s("%u", m);
    		_flushall();
    	printf("\t Enter number of repeated shifts: ");
    		scanf_s("%u", n);
    		_flushall();
    dec2(a, m, n);
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }
    
    // Функция поразрядного сдвига
    void dec2(long int var, unsigned int m, unsigned int n) {
    unsigned int b, i;
    long int mask = 1 << 31; 
    long int M[128];
    
    for (i = 0; i < n; ++i) 	
    M[i] = var << i*m;
    printf("\n\t Decimal and its binary equivalent after the shift:\n");
    
    for (i = 0; i < n; ++i)  {	
    	if (i == 0) {
    	printf("\n Initial number %ld:\n\t", M[0]);
    for (b = 1; b <= 32; ++b)  {
    	printf("%c", M[0]  mask ? '1' : '0');
    	M[0] <<= 1; // or: var = var << 1;
    	if (b % 8 == 0)
    		putchar(' '); }
    	} 
    else {
    		printf("\n The following number %ld:\n\t", M[i]);
    	for (b = 1; b <= 32; ++b) {
    	printf("%c", M[i]  mask ? '1' : '0');
    	M[i] <<= 1; 
    	if (b % 8 == 0)
    		putchar(' '); }
    }
    	printf("\n"); }
    }

    В программе предполагалось, что в наличии 32-разрядный компьютер. Кроме того, вывод двоичного эквивалента сделан побайтно, считая, что в одном байте находится 8 бит.

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

    (рис 16.9) Поразрядный сдвиг влево заданного числа

    Задание 9

  • Проверьте результат выполнения программы с помощью инженерного калькулятора (calc).
  • В программу добавьте нумерацию результатов вывода десятичного числа и его двоичного эквивалента.
  • Введите число своего дня рождения пользователя.
  • Проверьте программу при вводе отрицательных целых чисел.
  • Измените программу для поразрядного сдвига вправо.
  • Контрольные вопросы

  • Как осуществляется нумерация разрядов байта?
  • Для каких систем счисления в языке С имеются классификаторы форматируемых данных?
  • Какие логические поразрядные операции существуют в языке С?
  • Какие логические операции сдвига существуют в языке С? Какими операторами они реализуются?
  • Что такое битовое поле в языке С? Где оно может быть определено?
  • В чем отличие поразрядных и логических операторов НЕ, И и ИЛИ?
  • Как можно обменять значения двух целочисленных переменных без использования третьей переменной?
  • Чем отличается операция сдвига вправо для типов int и unsigned?
  • Вернуться к учебному плану