5.9 KiB
Задача 01 — битовые операции (разминка)
Первая задача дня: руки размялись, потом основная. Ориентир — 20–30 минут.
Глава I. Общая информация
- Цель: уверенно работать с битами — это ежедневный инструмент в embedded, сетях и протоколах.
- Почему это в Eltex: заголовки пакетов, маски, флаги, регистры — всё битовое. Вопросы вида «посчитай единичные биты» и «поменяй порядок байт» на собеседовании почти гарантированы.
- Что сдаётся:
solution.cpp, проходящийpython3 grade.py 01.
Глава II. Что нужно знать до старта
Четыре инструмента, которых достаточно:
x & (x - 1)— гасит самый младший единичный бит. Отсюда классика: число единиц можно считать циклом, покаxне станет нулём.x & 1— младший бит;x >> 1— сдвиг вправо.x & (1u << k)— проверка k-го бита.- Маски:
0x000000FF,0x0000FF00,0x00FF0000,0xFF000000— это четыре байта 32-битного числа. Комбинация «сдвинул и сложил» даёт любой порядок байт.
Глава 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→ UB, UBSAN ругается. - Забыт ноль в
is_power_of_two. - В
bswap32перепутаны направления сдвигов (влево/вправо) — проверь на0x11223344. - Использование
__builtin_popcount— запрещено условием: смысл в том, чтобы написать самому.
После сдачи
Разбор: почему x & (x-1) гасит младший бит; как эти приёмы используются в реальных
протоколах (маски подсетей, флаги TCP-заголовка, разбор полей IPv4).