Портативный способ разбиения n-байтового целого числа на отдельные байты

Проблема проста: возьмите 32-битное или 64-битное целое число и разделите его для отправки через (обычно) 1-байтовый интерфейс, такой как uart, spi или i2c.

Для этого я могу легко использовать битовую маску и сдвиг, чтобы получить то, что я хочу. Тем не менее, я хочу, чтобы это было переносимым, чтобы оно работало с прямым и прямым порядком байтов, а также для платформ, которые не отбрасывают биты, а поворот через перенос(маскировка избавляет от лишних битов, верно?).

Пример кода:

uint32_t value;
uint8_t buffer[4];
buffer[0] = (value >> 24) & 0xFF;
buffer[1] = (value >> 16) & 0xFF;
buffer[2] = (value >> 8) & 0xFF;
buffer[3] = value & 0xFF;

Я хочу гарантировать, что это работает на любой платформе, которая поддерживает 32-битные целые числа или более. Я не знаю, правильно ли это.


person Tryphon    schedule 18.01.2019    source источник
comment
Сдвиг и маскирование защищены порядком байтов.   -  person Weather Vane    schedule 18.01.2019
comment
Код хорош и переносится на современные машины. заставить его работать для платформ, которые не отбрасывают биты, а скорее переходят на другую сторону, как если бы это был круговой буфер, неясно. Пожалуйста, объясните подробнее об этом.   -  person chux - Reinstate Monica    schedule 18.01.2019
comment
Оператор >> всегда является сдвигом, а не вращением (т. е. он всегда отбрасывает самые правые биты).   -  person interjay    schedule 18.01.2019
comment
Не машинный код определяет поведение C-кода, а наоборот. Беззнаковые сдвиги в C гарантированно ведут себя как логические сдвиги, независимо от того, какие инструкции поддерживает ЦП. Результирующий машинный код может быть инструкцией ROR, но тогда перенос отбрасывается. Единственным исключением является знаковый сдвиг вправо для отрицательных чисел, где C не указывает, что произойдет, - тогда компилятор может использовать либо логический сдвиг, либо арифметический сдвиг.   -  person Lundin    schedule 18.01.2019
comment
Вы также можете попробовать с объединениями, используя что-то вроде union { uint32_t u32; uint8_t u8[4]; } t; t.u32 = value;   -  person Giovanni Cerretani    schedule 19.01.2019
comment
Разве использование союзов для этой цели не является нестандартным и полностью зависит от компилятора?   -  person Tryphon    schedule 19.01.2019
comment
Вероятно, вы правы, согласно stackoverflow. ком/вопросы/4540638/   -  person Giovanni Cerretani    schedule 20.01.2019


Ответы (1)


Код, который вы представили, является наиболее переносимым способом сделать это. Вы конвертируете одно целое число без знака шириной 32 бита в массив целых значений без знака шириной ровно 8 бит. Результирующие байты в массиве buffer имеют порядок байтов с обратным порядком байтов.

Маскировка не нужна. Из C11 6.5.7p5:

Результатом E1 >> E2 является битовая позиция E2 со сдвигом вправо E1. Если E1 имеет тип без знака или если E1 имеет
тип со знаком и неотрицательное значение, значение результата является целой частью частного
E1 / 2^E2.

и приведение к целому числу с шириной 8 бит (к значению) равно маскированию 8 бит. Итак, (result >> 24) & 0xff равно (uint8_t)(result >> 24) (значению). Поскольку вы назначаете переменную uint8_t, маскирование не требуется. В любом случае, я бы с уверенностью предположил, что он будет оптимизирован разумным компилятором.

Я могу порекомендовать взглянуть на одну реализацию, которую я помнил, которая, я думаю, реализовала действительно безопасным образом все возможные варианты разбиения и составления целых чисел фиксированной ширины до 64 бит из байтов и обратно, то есть в gpsd bits.h.

person KamilCuk    schedule 18.01.2019
comment
Маскировка часто необходима, чтобы заглушить предупреждения компилятора/статического анализатора. Например, средство проверки MISRA-C даже потребует, чтобы вы использовали (uint8_t)(value >> 24) с явным преобразованием через приведение. - person Lundin; 18.01.2019
comment
MISRA следует рассматривать как отдельный отдельный язык :). - person 0___________; 18.01.2019