|  |  | @ -0,0 +1,471 @@ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | # Лекция 7. Приемы низкоуровневого программирования | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Задачами низкого уровня в программировании называют такие, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | которые более связаны с особенностями работы компьютера, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | чем с предметной областью. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Примеры: программирование микроконтроллеров (Arduino, STM и т. п.), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | использование специфических возможностей операционной системы, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | работа с двоичными форматами данных, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | оптимизация производительности. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Задачи низкого уровня бывают как сложными, так и простыми, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | но всегда требуют большой внимательности. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Размер данных (`sizeof`) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Напомним, что тип данных определяется своим представлением в памяти | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | и допустимыми операциями над значениями этого типа. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Во многих случаях значения представляются в памяти | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | непрерывным блоком фиксированного размера. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, таковы фундаментальные типы: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | `char`, (`unsigned`) (`short`, `long`, `long long`) `int`, `float`, `double`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Узнать размер типа в байтах позволяет оператор `sizeof`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(char) << '\n';    // 1 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(int) << '\n';     // 4 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(float) << '\n';   // 4 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(double) << '\n';  // 8 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Оператор `sizeof` можно применять не только к типам, но и к переменным — | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | в этом случае он даст размер типа переменной-операнда. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | На самом деле, только размер `sizeof(char)` гарантируется. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Размеры других встроенных типов могут отличаться в зависимости от платформы | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | (процессора, операционной системы). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Есть специальные типы, размер которых в битах фиксирован: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #include <cstdint> | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(uint8_t)  << '\n';  // 1 байт  =  8 бит | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(uint16_t) << '\n';  // 2 байта = 16 бит | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(uint32_t) << '\n';  // 4 байта = 32 бита | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << sizeof(uint64_t) << '\n';  // 8 байт  = 64 бита | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Размер массива равен произведению размера элемента на количество элементов. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, `uint32_t array[30]` имеет размер `120 == 30 * sizeof(uint32_t)`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Размер указателя зависит от платформы, но фиксирован (4 или 8). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Оператор `sizeof` относится к типу, а типы есть только на этапе компиляции. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Можно ли применить `sizeof` к переменной, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | которая хранит динамически определяемое количество данных, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | например, к `string` или `vector<T>`? | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Формально можно. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Но результат зависит только от внутреннего устройства этих типов, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | а не от того, что они хранят во время выполнения программы. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | То есть результат бесполезен. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Как правило, применять `sizeof` к таким типам — это ошибка, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | хотя компилятор о ней и не предупредит. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Системы счисления | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Пусть в памяти хранится целое число: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t x = 1234; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Одно и то же значение можно представить в разных системах счисления. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Само значение всегда хранится как биты, а представление выбирают удобное. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если интересны биты числа, удобна двоичная система: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #include <iomanip> | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | using namespace std; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // ... | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << bin << x; // 10011010010 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Действительно: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 1       0      0      1      1      0      1      0      0      1      0 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 1×2¹⁰ + 0×2⁹ + 0×2⁸ + 1×2⁷ + 1×2⁶ + 0×2⁵ + 1×2⁴ + 0×2³ + 0×2² + 1×2¹ + 0×2⁰ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 1024  +               128  + 64   +        16   +                 2         = 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Чтобы записать в коде программы константу в двоичной системе, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | используется префикс `0b`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t y = 0b10011010010; // 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Однако двоичные константы неудобно читать, они слишком длинные. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Принято вместо них пользоваться шестнадцатеричными с префиксом `0x`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t z = 0x4d2; // 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | В шестнадцатеричной системе используются цифры от 0 до 9, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | а также латинские буквы для недостающих цифр: A₁₆ = 10₁₀, B, C, D, E, F₁₆ = 15₁₀. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Действительно: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 4        d       2 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 4×16² + 13×16¹ + 2×16⁰ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 1024  + 208    + 2     = 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Между двоичной и шестнадцатеричной системой можно переводить числа проще: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | каждые 4 двоичных цифры соответствуют одной шестнадцатеричной: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 0b 100 1101 0010 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 0x   4    d    2 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | В коде можно группировать цифры числа в любой системе счисления | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | для читаемости: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t w = 0b100'1101'0010; // 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Менее распространена восьмеричная форма записи с префиксом `0o` или `0`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t v = 0o2322; // 1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Однако помнить о ней надо, потому что | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | **целые числа с ведущими нулями считаются записанными в восьмеричной системе.** | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t u = 01234; // 668 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Побитовые операции | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Побитовые операции `&`, `|`, `^` и `~` применяются к целым числам | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | и производят действие попарно над всеми битами. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | `&` — логическое «И», или логическое умножение, или конъюнкция, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | дает 1, если оба операнда равны 1. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | `|` — логическое «ИЛИ», или логическое сложение, или дизъюнкция, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | дает 1, если хотя бы один операнд равен 1. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | `^` — исключающее «ИЛИ», дает 1, если 1 равен строго один операнд. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | `~` — логическое «НЕ», или инверсия, дает 1 для 0 и 0 для 1. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Таблицы истинности: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |`a`|`b`|`a & b`|`a | b`|`a ^ b`|`~a`| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |---|---|---|---|---|---| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |0|0|0|0|0|1| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |0|1|0|1|1|1| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |1|0|0|1|1|0| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | |1|1|1|1|0|0| | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Не следует путать побитовые операции и логические `&&`, `||`, `^^` и `!`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Логические операции работают со значениями типа `bool`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если применить их к целочисленным значениям, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | операнды будут сначала преобразованы к `bool` (0 → `false`, иначе `true`), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | после чего будет посчитан результат тоже типа `bool`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint8_t x = 42;     //       0b00101010 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << ~x << '\n'; // 214 = b011010101 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | cout << !x << '\n'; // 0 = !true = !(42 != 0) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Битовые операции часто применяются при работе с бинарными (двоичными) данными, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | полученными по сети, считанными с датчиков, или загружаемыми из файла. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Бинарные форматы используется в этих случаях для экономии канала или диска. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, если на целое число отведено 32 бита, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | в них можно сохранить числа от 0 до 2³²-1 в бинарном формате. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если бы формат был текстовым, то 32 бит, или 4 байт, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | хватило бы всего на 4 символа, то есть на числа всего от 0 до 9999. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Рассмотрим пример из области двоичных файлов. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | В изображении формата BMP пиксель может быть представлен как 16-битное целое. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | При этом в одном пикселе закодированы значения трех компонент цвета: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | красной (red), зеленой (green) и голубой (blue). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Красная компонента содержится в младших пяти битах. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Голубая компонента содержится в старших пяти битах. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Зеленая компонента содержится в средних шести битах. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Иллюстрация для конкретного значения: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint16_t color = 0x1234; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint8_t r, g, b; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // color = 0b0001'0010'0011'0100 = 0b00010'010001'10100 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // r = 0b10100 = 0x14 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // g = 0b10001 = 0x11 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // b = 0b00010 = 0x02 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Задача: извлечь из переменной `color` значения `r`, `g`, и `b`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Заметим, что `r` представляет собой `color`, у которого все биты, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | кроме младших пяти, заменены на нули, а младшие пять сохранены, как есть. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Обозначим биты, которые надо сохранить, как `1`, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | а биты, которые надо обнулить, как `0`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | color = 0b00010'010001'10100 = 0x1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  mask = 0b00000'000000'11111 = 0x001F | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     r = 0b00000'000000'10100 = 0x0014 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Какую операцию `F` можно применить, чтобы выполнялось `color F mask == r`? | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Для сочетания любого бита с 0 она должна давать 0: `F(x, 0) = 0`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Для сочетания любого бита с 1 она должна давать тот же бит: `F(x, 1) = x`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Легко видеть, что таким свойством обладает `&`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | r = color & 0x1F; // 0x14 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Значение, где нужные биты обозначены 0, а не нужные — 1, называется *маской,* | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | а операция конъюнкции в этом случае — *наложением маски.* | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Так говорят по аналогии с трафаретом, через прорези которого видно только нужное. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Как получить компоненту зеленого цвета `g`? | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Можно опять начать с наложения маски: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | color = 0b00010'010001'10100 = 0x1234 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  mask = 0b00000'111111'00000 = 0x07E0 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | value = 0b00000'010001'00000 = 0x0220 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Результат наложения маски, `value`, не совпадает с ожидаемым значением `g`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Причина в том, что это правильные биты, но на неправильных местах. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Чтобы получить значение `g`, нужно биты `value` переместить в младшие разряды. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | g = value >> 5; // 0x11 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Можно было бы сделать все одним выражением: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | g = (color & 0x7E0) >> 5; // 0x11 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Приоритет побитовых операций низкий, поэтому требуются скобки. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | И вообще, код с побитовыми операциями относится к сложному, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | поэтому лучше ставить скобки, даже если это было бы необязательно, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | чтобы читателю не вспоминать приоритеты. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Как получить компоненту голубого цвета `b`? | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Сдвинуть старшие 5 бит на позиции младших бит: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | b = color >> 11; // 0x02 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Порядок байт в машинном слове (byte order, endianness) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | При записи чисел принято писать старшие разряды в начале. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, 513 — сначала сотни (5), затем десятки (1), затем единицы (3). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | В памяти минимально адресуемая единица не разряд, а байт, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | то есть 513 (в шестнадцатеричной системе `0x0201`) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | представлено как два байта: `0x02` и `0x01`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | В памяти их можно расположить как `0x02`, `0x01` — | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | от старшего к младшему (big-endian) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | или как `0x01`, `0x02` — от младшего к старшему (little-endian). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Это выбирают разработчики процессора, а программисту их выбор безразличен: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | пока работа ведется через переменные, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | внутреннее представление всегда будет использоваться одно и то же. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Эксперимент: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #include <cstdint> | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #include <iostream> | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | using namespace std; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | int | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | main() { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint16_t u16 = 0x0201; // 513 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint8_t* u8 = reinterpret_cast<uint8_t*>(&u16); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     for (size_t i = 0; i < sizeof(u16); i++) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         cout << "u16 byte " << i << " = " << u8[i] << "\n"; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Результат на Intel Core i7: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | u16 byte 0 = 1 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | u16 byte 1 = 2 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Действительно, архитектура x86 (у Intel и AMD) использует little-endian. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Порядок байт нужно учитывать, если данные передаются между машинами: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | в двоичных файлах или двоичными протоколами по сети. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Во-первых, это означает, что в описании формата файла или сетевого протокола | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | должно быть сказано, какой порядок байт используется. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Исторически сложилось, что большинство протоколов используют big-endian, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | поэтому его еще называют сетевым порядком байт (network byte order). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | «Сетевой» — это термин, сама по себе передача по сети не влияет на порядок байт. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Поэтому, во-вторых, если данные прибывают или читаются в ином порядке, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | чем используется локально, нужно преобразовывать его, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | чтобы получить правильное значение. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Пусть в некотором сетевом протоколе | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | сначала передается целое 32-битное число в big-endian (длина), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | затем столько байт данных, чему равно это число. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, передается число 5 и 5 байтов `H`, `e`, `l`, `l`, `o`: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 00 00 00 05 48 65 6c 6c 6f | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ----------- -- -- -- -- -- | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |           5  H  e  l  l  0 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Пусть они приняты в некий буфер — массив байт: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint8_t payload[] = {0x00, 0x00, 0x00, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Скопируем первые 4 байта в переменную целочисленного типа: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #include <cstring> | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // ... | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint32_t length; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | std::memcpy(&length, payload, sizeof(length));  // куда, откуда, сколько байт | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если теперь проверить значение `length`, оно будет не 5, а 83886080 (0x05000000)! | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Обычно порядок байт преобразуют после чтения. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Имеем `0xXXYYZZWW`, нужно преобразовать его в `0xWWZZYYXX`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Для этого нужно выделить нужные байты масками, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | затем сдвинуть их на те позиции, которые нужны: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | →| 8|← | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   XX YY ZZ WW | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |    | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   XX 00 00 00 >> 24 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   00 YY 00 00 >>  8 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   00 00 ZZ 00 <<  8 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   00 00 00 WW << 24 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |    | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   WW ZZ YY XX | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | То же самое на C++: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint32_t xx = length & 0xFF000000; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint32_t yy = length & 0x00FF0000; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint32_t zz = length & 0x0000FF00; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint32_t ww = length & 0x000000FF; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | length = (xx >> 24) | (yy >> 8) | (zz << 8) | (ww << 24); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | На практике вручную эти манипуляции обычно не делают. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, в сетевом программировании применяются функции `htonl()`/`ntohl()`, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | которые делают то же, что расписано выше. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Важно помнить, когда требуется преобразование. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Выравнивание данных и упаковка структур | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Рассмотрим структуру: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | struct Foo { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint32_t bar; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint8_t baz; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint32_t quux; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | }; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Можно предположить, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | что `sizeof(Foo) == 9 == sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t)`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Но эксперимент покажет `sizeof(Foo) == 12` или `16`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если распечатать байты, как в эксперименте с порядком байт, увидим: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 4 байта   1   1   1   1   4 байта | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | --------  --  --  --  --  --------  | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | bar       baz xx  xx  xx  quux | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Откуда появились три лишних байта, обозначенные `xx xx xx`? | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Дело в том, что во многих процессорах есть разница, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | по какому адресу расположена переменная (или поле структуры). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, если адрес кратен размеру переменной (4 для `uint32_t`), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | то обращение к переменной более эффективно. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Такие переменные называют *выровненными (aligned).* | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Некоторые процессоры вообще запрещают обращения к невыровненным данным. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | По умолчанию компилятор располагает поля структур для большей скорости, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | жертвуя неиспользуемыми байтами. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Они называются байтами выравнивания *(padding).* | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если они в середине структуры, их иногда называют *дырами (holes).* | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Обычно про выравнивание не нужно думать. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Если размер структуры критичен, например, их будет в программе очень много, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | стоит расположить поля так, чтобы компилятору не приходилось выравнивать их, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | а потом проверить результат `sizeof`. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Еще один случай, когда нужно управлять выравниванием — | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | если структура читается из файла или из сети, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | и в формате файла или в описании протокола выравнивание не предусмотрено. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Например, если указано, что 4, 1 и 4 байта `bar`, `baz` и `quux` идут подряд. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Тогда, если скопировать структуру через `std::memcpy()` из буфера, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | то часть `quux` окажется в неиспользуемой области, что неправильно: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | uint8_t payload[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                      ------------------  ---  ------------------ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                      bar                 baz  quux    | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                      ↓                   ↓    ↓ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                      ------------------  ---  ---  ---  ---  ------------------ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Foo foo.             bar                 baz  xx   xx   xx   quux | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Чтобы запретить компилятору вставлять padding, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | используются специальные директивы: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #pragma pack(push, 1)   // Структуры в коде ниже будут упакованы (alignment=1). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | struct Foo { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint32_t bar; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint8_t baz; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     uint32_t quux; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | }; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | #pragma pack(pop)       // Структуры в коде ниже не будут упаковываться | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                         // с alignment=1. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Некоторые компиляторы делают это иначе — атрибутами: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ```cpp | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | struct Foo { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // ... | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | } __attribute__((pack(1)); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ``` | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | Напомним, что компилятор по умолчанию делает выравнивание для оптимизации, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | поэтому упаковывать структуры нужно только в том случае, если это необходимо. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ## Ресурсы | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | * [Статья, аналогичная лекции](https://habr.com/ru/companies/ruvds/articles/735668/) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | * [Трюки с битами](https://graphics.stanford.edu/~seander/bithacks.html) |