Из ISO/IEC 9899:TC3, 6.3.1.3 Целые числа со знаком и без знака (согласно 6.3 Преобразования):
- Когда значение целочисленного типа преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно
не изменился.
- В противном случае, если новый тип не имеет знака, значение преобразуется путем многократного добавления или вычитания на единицу больше, чем максимальное значение, которое
может быть представлен в новом типе до тех пор, пока значение не окажется в диапазоне
новый тип.
Хотя это звучит немного запутанно, это отвечает на следующий вопрос.
Зачем нужен & 0xff, если он преобразован в u8?
В этом нет необходимости, поскольку приведение автоматически выполняет маскирование.
Когда дело доходит до вопроса в теме, последнее предложение ОП:
#define HIBYTE(w) ((uint8_t)(((uint16_t)(w) >> 8)))
#define LOBYTE(w) ((uint8_t)(w))
Это будет работать должным образом для всех значений без знака. Значения со знаком всегда будут преобразованы макросами в значения без знака, что в случае дополнения до двух не изменит представление, поэтому результаты вычислений хорошо определены. Однако если предположить, что дополнение до двух не является переносимым, решение не является строго переносимым для целых чисел со знаком.
Реализовать переносимое решение для целых чисел со знаком было бы довольно сложно, и можно даже поставить под сомнение смысл такой реализации:
- Должен ли результат быть подписанным или беззнаковым?
- Если результат должен быть беззнаковым, он на самом деле не квалифицируется как старший/младший байт исходного числа, поскольку для его получения может потребоваться изменение представления.
- Если результат должен быть подписан, это должно быть дополнительно указано. Результат
>>
для отрицательных значений, например, определяется реализацией, поэтому получение переносимого четко определенного «старшего байта» кажется сложной задачей. Следует действительно задаться вопросом о цели такого расчета.
И так как мы играем в юриста по языку, мы могли бы задаться вопросом о подписи левого операнда (uint16_t)(w) >> 8
. Unsigned может показаться очевидным ответом, но это не так из-за правил целочисленного продвижения.
Целое преобразование применяется, среди прочего, к объектам или выражениям, указанным ниже.
Объект или выражение с целочисленным типом, ранг целочисленного преобразования которого меньше или равен рангу int и unsigned int.
Правило целочисленного повышения в таком случае указывается как:
Если целое число может представлять все значения исходного типа, значение преобразуется в целое число;
Это будет иметь место для левого операнда на типичной 32-битной или 64-битной машине.
К счастью, в таком случае левый операнд после преобразования по-прежнему будет неотрицательным, что делает результат >>
вполне определенным:
Результатом E1 >> E2 является бит E2 со сдвигом вправо в E1. Если E1 имеет тип без знака или если E1 имеет тип со знаком и неотрицательное значение, значение результата является целой частью частного E1 / 2E2.
Добавлено несколько тегов, потому что, если это не о том, как избежать каких-либо скрытых ошибок, это слишком субъективно для переполнения стека.
Обратите внимание, что
uint8_t
,uint16_t
и т. д. являются необязательными; реализация не обязана даже иметь целые типы этих конкретных ширин. (Например,unsigned char
не обязательно должен быть 8-битным, и существуют системы, где он 9-битный или 32-битный.) Поэтому их нельзя использовать в действительно «кросс-платформенном» решении.@NateEldredge Предположим, что они определены.
"Зачем нужен &0xff, если он залит на u8?" --> Пояс и подтяжки. Предпочтителен только
(uint8_t)
, так как он определяет тип.При рассмотрении различных пакетов SDK кажется, что LOBYTE и HIBYTE редко бывают совместимыми, как показано ниже. На самом деле они логически идентичны и, следовательно, непротиворечивы. Младший байт всегда представляет собой полное 16-битное значение, усеченное до младших 8 бит. Старший байт всегда представляет собой полное 16-битное значение, сдвинутое вправо на 8 бит, а затем усеченное до младших 8 бит.
Обратите внимание, что
u8
иu16
являются дополнительными необязательными определениями типов для необязательныхuint8_t
иuint16_t
.uint8_t
иuint16_t
с гораздо большей вероятностью будут предоставлены через заголовокstdint.h
. Учитывая все три ваших примера, наиболее переносимым является вариант сuint8_t
иuint16_t
.