Вот простая функция, которая пытается прочитать общее целое число из двух дополнений из буфера с прямым порядком байтов, где мы предположим, std::is_signed_v<INT_T>
:
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
INT_T result = 0;
for (size_t i = 0; i < sizeof(INT_T); i++) {
result <<= 8;
result |= *data;
data++;
}
return result;
}
К сожалению, это неопределенное поведение, так как последнее <<=
сдвигается в знак бит.
Итак, теперь мы попробуем следующее:
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
std::make_unsigned_t<INT_T> result = 0;
for (size_t i = 0; i < sizeof(INT_T); i++) {
result <<= 8;
result |= *data;
data++;
}
return static_cast<INT_T>(result);
}
Но сейчас мы вызываем определяемое реализацией поведение в static_cast
, переводя из неподписанного в подписанное.
Как я могу сделать это, оставаясь в «четко определенной» сфере?
Начните с объединения байтов в беззнаковое значение. Если вам не нужно собирать группы из 9 или более октетов, соответствующая реализация C99 гарантированно будет иметь такой тип, который достаточно велик, чтобы вместить их все (реализация C89 гарантированно будет иметь тип unsigned, достаточно большой, чтобы содержать как минимум четыре ).
В большинстве случаев, когда вы хотите преобразовать последовательность октетов в число, вы будете знать, сколько октетов вы ожидаете. Если данные закодированы как 4 байта, вы должны использовать четыре байта независимо от размеров int
а также long
(переносимая функция должна возвращать тип long
).
unsigned long octets_to_unsigned32_little_endian(unsigned char *p)
{
return p[0] |
((unsigned)p[1]<<8) |
((unsigned long)p[2]<<16) |
((unsigned long)p[3]<<24);
}
long octets_to_signed32_little_endian(unsigned char *p)
{
unsigned long as_unsigned = octets_to_unsigned32_little_endian(p);
if (as_unsigned < 0x80000000)
return as_unsigned;
else
return (long)(as_unsigned^0x80000000UL)-0x40000000L-0x40000000L;
}
Обратите внимание, что вычитание выполняется в виде двух частей, каждая из которых находится в диапазоне длинны со знаком, для обеспечения возможности систем, в которых LNG_MIN
это -2147483647. Попытка преобразовать последовательность байтов {0,0,0,0×80} в такой системе может привести к неопределенному поведению [так как при этом будет вычислено значение -2147483648], но код должен обрабатывать полностью переносимым образом все значения, которые будут находиться в диапазоне «долго».
Других решений пока нет …