8.1 KiB
Задача 01 — битовые операции (разминка)
Первая задача дня: руки размялись, потом основная. Ориентир — 20–30 минут.
Глава I. Общая информация
- Цель: уверенно работать с битами — это ежедневный инструмент в embedded, сетях и протоколах.
- Почему это в Eltex: заголовки пакетов, маски, флаги, регистры — всё битовое. Вопросы
вида «посчитай единичные биты» и «поменяй порядок байт» на собеседовании почти гарантированы.
На практике
bswapнужен ровно потому, что сеть передаёт многобайтовые поля в network byte order (big-endian), а x86 внутри — little-endian: без разворота байт число читается неверно. - Что сдаётся:
solution.cpp, проходящийpython3 grade.py 01.
Глава II. Что нужно знать до старта
Четыре инструмента, которых достаточно:
x & (x - 1)— гасит самый младший единичный бит. Механизм: в дополнительном кодеx - 1переворачивает все нули справа от младшего единичного бита в единицы, а сам этот бит — в ноль, биты выше не трогает; операция&с исходнымxпоэтому обнуляет ровно один бит — младший единичный. Отсюда классика: число единиц можно считать циклом, покаxне станет нулём, и число итераций равно числу единичных бит, а не 32.x & 1— младший бит;x >> 1— сдвиг вправо.x & (1u << k)— проверка k-го бита.- Маски:
0x000000FF,0x0000FF00,0x00FF0000,0xFF000000— это четыре байта 32-битного числа. Комбинация «сдвинул и сложил» даёт любой порядок байт.
Почему дальше: та же логика масок и сдвигов нужна в задаче 04 — разбор IPv4-заголовка byte-по-byte.
Глава III. Задание
Реализуй в solution.cpp четыре функции. Встроенные __builtin_popcount, std::popcount,
std::byteswap использовать нельзя — нужна своя реализация (любая корректная).
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. Подсказки (после первой попытки)
UBSAN жалуется на сдвиг
Сдвиг знакового числа в старший бит — неопределённое поведение. Все промежуточные значения
держи в uint32_t и пиши константы с суффиксом u: 1u << 31.
is_power_of_two возвращает true на нуле
У нуля нет единичных битов, поэтому x & (x-1) даёт 0 — как и у степеней двойки. Добавь
явную проверку x != 0.
reverse_bits32 работает «почти»
Проверь порядок операций в цикле: сначала ставим бит в результат, потом сдвигаем источник. Если перепутать — биты уедут на одну позицию.
Глава 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).