Files
eduplan-cpp-eltex/diag/tasks/01_bits/task.md
T

117 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задача 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).