ПрограммированиеРазработчик низкоуровневого ПО, SI/Embedded C developer

Расскажите, как реализуется оператор циклического сдвига (rotary shift, circular shift) в языке C. Почему нет стандартного оператора для этой операции в C, и как реализовать безопасный цикл сдвига для целых чисел любого размера?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Операция циклического (rotary/circular) сдвига — это смещение битов числа на определённое количество разрядов с переносом «выпавших» бит в противоположный конец. В языке C нет встроенного оператора для этой задачи; такое решение исторически связано с переносимостью стандартной библиотеки и необходимостью явно определять поведение для разных платформ.

Проблема состоит в том, что стандартные операторы сдвига (<<, >>) не делают сдвиг циклическим: они лишь сдвигают биты, а «выпавшие» заменяют нулями. Для циклического сдвига нужно явно скомбинировать результаты двух сдвигов и маскировать результат заданным количеством бит.

Решение — реализовать циклический сдвиг вручную. Для 32-разрядного unsigned числа это выглядит так:

uint32_t rotate_left(uint32_t value, unsigned int shift) { return (value << shift) | (value >> (32 - shift)); } uint32_t rotate_right(uint32_t value, unsigned int shift) { return (value >> shift) | (value << (32 - shift)); }

Ключевые особенности:

  • Нет встроенного оператора rotary shift в C.
  • Нужно вручную комбинировать два сдвига и маску.
  • Следует учитывать размер типа данных (битность) для переносимости.

Вопросы с подвохом.

Может ли оператор << или >> реализовать циклический сдвиг без дополнительных операций?

Нет. Обычные сдвиги заменяют вышедшие за пределы разряды нулями, а не переносят их на другую сторону.

Что произойдёт, если сделать сдвиг на размер типа (shift равен количеству бит в числе)?

Поведение не определено (Undefined Behavior по стандарту C), нужно обязательно делать сдвиг по модулю размера типа.

Для signed типов безопасно делать rotary shift этими функциями?

Нет, всегда используйте unsigned типы, так как побитовые сдвиги для signed типов ведут себя по-разному в зависимости от компилятора и архитектуры.

Типовые ошибки и анти-паттерны

  • Использование signed типов вместо unsigned для битовых сдвигов.
  • Неучёт размера типа (например, 32 против 64 бит).
  • Отсутствие маскировки shift (например, не делать shift = shift % 32 для 32-разрядных чисел).

Пример из жизни

Негативный кейс

Использование обычного сдвига для циклического смещения:

uint32_t x = 0xFA3C0F00; uint32_t y = x << 5; // Не носит циклический характер

Плюсы:

  • Простота написания.

Минусы:

  • Биты теряются, поведение не соответствует ожиданиям, трудно уловимый баг в криптопрограммах и обработке данных.

Позитивный кейс

Использование ручной функции rotary shift с учётом размера типа и защиты от UB:

uint32_t x = 0xFA3C0F00; uint32_t y = rotate_left(x, 5);

Плюсы:

  • Корректный результат на всех платформах, защита от неопределённого поведения.

Минусы:

  • Небольшая логическая сложность, более длинный код.