117 lines
8.1 KiB
Markdown
117 lines
8.1 KiB
Markdown
# Задача 01 — битовые операции (разминка)
|
||
|
||
> Первая задача дня: руки размялись, потом основная. Ориентир — 20–30 минут.
|
||
|
||
## Глава I. Общая информация
|
||
|
||
- **Цель:** уверенно работать с битами — это ежедневный инструмент в embedded, сетях и
|
||
протоколах.
|
||
- **Почему это в Eltex:** заголовки пакетов, маски, флаги, регистры — всё битовое. Вопросы
|
||
вида «посчитай единичные биты» и «поменяй порядок байт» на собеседовании почти гарантированы.
|
||
На практике `bswap` нужен ровно потому, что сеть передаёт многобайтовые поля в network byte
|
||
order (big-endian), а x86 внутри — little-endian: без разворота байт число читается неверно.
|
||
- **Что сдаётся:** `solution.cpp`, проходящий `python3 grade.py 01`.
|
||
|
||
## Глава II. Что нужно знать до старта
|
||
|
||
Четыре инструмента, которых достаточно:
|
||
|
||
1. `x & (x - 1)` — гасит самый младший единичный бит. Механизм: в дополнительном коде `x - 1`
|
||
переворачивает все нули справа от младшего единичного бита в единицы, а сам этот бит — в ноль,
|
||
биты выше не трогает; операция `&` с исходным `x` поэтому обнуляет ровно один бит — младший
|
||
единичный. Отсюда классика: число единиц можно считать циклом, пока `x` не станет нулём, и
|
||
число итераций равно числу единичных бит, а не 32.
|
||
2. `x & 1` — младший бит; `x >> 1` — сдвиг вправо.
|
||
3. `x & (1u << k)` — проверка k-го бита.
|
||
4. Маски: `0x000000FF`, `0x0000FF00`, `0x00FF0000`, `0xFF000000` — это четыре байта 32-битного
|
||
числа. Комбинация «сдвинул и сложил» даёт любой порядок байт.
|
||
|
||
Почему дальше: та же логика масок и сдвигов нужна в задаче 04 — разбор IPv4-заголовка byte-по-byte.
|
||
|
||
## Глава III. Задание
|
||
|
||
Реализуй в `solution.cpp` четыре функции. Встроенные `__builtin_popcount`, `std::popcount`,
|
||
`std::byteswap` использовать нельзя — нужна своя реализация (любая корректная).
|
||
|
||
```c
|
||
int popcount32(uint32_t x); // число единичных бит
|
||
uint32_t reverse_bits32(uint32_t x); // развернуть порядок всех 32 бит
|
||
bool is_power_of_two(uint32_t x); // x — степень двойки (0 не считается)
|
||
uint32_t bswap32(uint32_t x); // поменять порядок байт местами
|
||
```
|
||
|
||
Полезно сразу проверять себя руками: `bswap32(0x11223344) == 0x44332211`,
|
||
`popcount32(0b1011) == 3`, `is_power_of_two(1) == true`, `is_power_of_two(0) == false`,
|
||
`reverse_bits32(1) == 0x80000000`.
|
||
|
||
## Глава IV. Ступени
|
||
|
||
**Ступень 1 (5 минут).** `is_power_of_two` — самая простая. Подумай, чем степень двойки
|
||
отличается от остальных чисел: у неё ровно один единичный бит. Значит подойдёт приём
|
||
`x & (x - 1)`. Отдельно обработай `0` (он не степень двойки) и проверь на `1` и на `0x80000000`.
|
||
|
||
**Ступень 2 (10 минут).** `popcount32` — цикл: пока `x != 0`, гаси младший бит и считай
|
||
итерации. Альтернатива — 32 сдвига с проверкой младшего бита; оба варианта верные, первый
|
||
в среднем быстрее.
|
||
|
||
**Ступень 3 (10 минут).** `reverse_bits32` — цикл на 32 итерации: берём младший бит `x`,
|
||
ставим его в результат на позицию `31 - i`, сдвигаем `x`. Внимание на типы: сдвиг `1u << 31`
|
||
для знакового `int` — UB, поэтому работай в `uint32_t`.
|
||
|
||
**Ступень 4 (10 минут).** `bswap32` — четыре байта. Самый наглядный вариант: маски и сдвиги,
|
||
`((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8) | ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24)`.
|
||
|
||
**Ступень 5.** `python3 grade.py 01` — пока не станет `PASS`.
|
||
|
||
## Глава V. Критерии приёмки
|
||
|
||
`PASS` от `grade.py 01`: все проверки `ok`, сборка без предупреждений, ASAN/UBSAN чистые.
|
||
|
||
## Глава VI. Подсказки (после первой попытки)
|
||
|
||
<details>
|
||
<summary>UBSAN жалуется на сдвиг</summary>
|
||
|
||
Сдвиг знакового числа в старший бит — неопределённое поведение. Все промежуточные значения
|
||
держи в `uint32_t` и пиши константы с суффиксом `u`: `1u << 31`.
|
||
</details>
|
||
|
||
<details>
|
||
<summary>is_power_of_two возвращает true на нуле</summary>
|
||
|
||
У нуля нет единичных битов, поэтому `x & (x-1)` даёт 0 — как и у степеней двойки. Добавь
|
||
явную проверку `x != 0`.
|
||
</details>
|
||
|
||
<details>
|
||
<summary>reverse_bits32 работает «почти»</summary>
|
||
|
||
Проверь порядок операций в цикле: сначала ставим бит в результат, потом сдвигаем источник.
|
||
Если перепутать — биты уедут на одну позицию.
|
||
</details>
|
||
|
||
## Глава VII. Ловушки
|
||
|
||
- Сдвиги в `int` вместо `uint32_t` → сдвиг `1 << 31` для знакового типа — UB → UBSAN падает
|
||
на этой строке с диагностикой конкретного сдвига.
|
||
- Забыт ноль в `is_power_of_two` → `0 & (0 - 1) == 0`, функция возвращает `true` на нуле →
|
||
тест на `x == 0` красный.
|
||
- В `bswap32` перепутаны направления сдвигов (влево/вправо) → байты встают не на свои места →
|
||
`bswap32(0x11223344) != 0x44332211`, видно прямым сравнением.
|
||
- Использование `__builtin_popcount` — запрещено условием: смысл в том, чтобы написать самому;
|
||
формально тест это не всегда ловит по выводу, но нарушает условие задачи.
|
||
|
||
**Факты для карточек**
|
||
- base | Сколько байт меняет местами `bswap32`? — 4
|
||
- core | Что делает `x & (x - 1)`? — гасит младший единичный бит `x`
|
||
- core | Сколько итераций цикла `popcount32` на `x = 0xFFFFFFFF`? — 32 (по числу единичных бит)
|
||
- deep | Почему `1u << 31` пишут с суффиксом `u`, а не как `int`? — сдвиг знакового `int` в
|
||
знаковый бит — UB, `uint32_t` определён стандартом для любых сдвигов в пределах разрядности
|
||
- base | Какой порядок байт использует сеть для многобайтовых полей? — network byte order
|
||
(big-endian)
|
||
|
||
## После сдачи
|
||
|
||
Разбор: почему `x & (x-1)` гасит младший бит; как эти приёмы используются в реальных
|
||
протоколах (маски подсетей, флаги TCP-заголовка, разбор полей IPv4).
|