Как преобразовать 2 байта в подписанный шорт в C - PullRequest
0 голосов
/ 22 июня 2019

У меня есть 2 байта, которые мне нужно преобразовать в подписанный короткий номер.Например, у меня есть отдельные байты (0000 0001) и (0000 0002) в двоичном виде.Как я могу преобразовать их в подписанное короткое значение?

Ответы [ 3 ]

2 голосов
/ 22 июня 2019

Если байты содержатся в подписанном типе данных, таком как signed char или int8_t, то это довольно просто:

signed short combine_signed(signed char byte1, signed char byte2) {
  return byte1 * 256 + (uint8_t)byte2;
}

Здесь используется умножение, а не операция сдвига, но ожидается, что компилятор фактически вставит соответствующую операцию сдвига. Стандарт C не определяет результат сдвига влево отрицательного числа, поэтому сдвиг влево нельзя использовать в переносимом коде.

Если байты имеют тип без знака или тип шире, чем 8 бит, то самый простой подход - сначала преобразовать старший байт в значение со знаком, а затем продолжить, как описано выше. Преобразование в значение со знаком не может быть выполнено простым преобразованием, потому что такое преобразование будет переполнением целого числа, результаты которого не определены стандартом C. Таким образом, переносимая программа должна явно проверять старший бит:

signed short combine(int byte1, int byte2) {
  // This code assumes that byte1 is in range, but allows for the possibility
  // that the values were originally in a signed char and so is now negative.
  if (byte1 >= 128) byte1 -= 256;
  return byte1 * 256 + (uint8_t)byte2;
}

(Как gcc, так и clang для x86, скомпилированные с -O2 или лучше, могут преобразовать это в простую последовательность из трех инструкций без умножения или условия.)

1 голос
/ 22 июня 2019

Дано:

char msb = 0x01 ;
char lsb = 0x02 ;

Тогда:

short word = (msb << 8) | (lsb & 0xff) ;

приведет к тому, что word будет иметь значение 0x0102 (или 258 10 ).

Поскольку вы попросили подписать короткую заявку, это не очень интересный пример. Для:

char msb = 0x80 ;
char lsb = 0x02 ;

word будет иметь 0x8002, что для 16-битного short будет -32766.

Однако в реализации, где short длиннее 16 бит (что разрешено), результат будет интерпретироваться как +32770. В этом случае гораздо безопаснее использовать тип int16_t фиксированного размера, определенный в stdint.h, чтобы избежать потенциальной зависимости от реализации.

 int16_t word = (msb << 8) | (lsb & 0xff) ;

Это можно несколько упростить, используя uint8_t вместо char, который может быть либо подписанным, либо без знака:

uint8_t msb = 0x80u ;
uint8_t lsb = 0xFFu ;
int16_t word = (msb << 8) | lsb ;

Результатом будет word = -32513, тогда как если бы lsb и msb были char и char были подписаны в реализации, то результатом было бы -1 из-за неявного продвижения типа и знака расширение lsb.

Это остается не совсем четко определенным, поскольку левое выражение повышается до unsigned int и может привести к значению, не представляемому как int16_t, и в этом случае поведение определяется реализацией. Тем не менее, это была бы необычная реализация, которая делала что-то другое, а не просто копировала биты дословно, именно поэтому она работает, а вышесказанное идиоматично.

Если явно требуется short, чтобы гарантировать правильно подписанный результат независимо от длины short, вы можете явно привести к int16_t и присвоить short (или даже int):

 short word = (int16_t)((msb << 8) | (lsb & 0xFF));

Решение также возможно с использованием объединения, но, учитывая теги по этому вопросу, маловероятно, что это приемлемое решение в этом случае. Он имеет преимущество, заключающееся в том, что он избегает любого поведения, определенного реализацией, а также продвижения тайных типов и неявных правил преобразования, но вам приходится иметь дело с порядком байтов:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  #define LSB 0
  #define MSB 1
#else
  #define LSB 1
  #define MSB 0
#endif

union
{
    int16_t word ;
    uint8_t byte[2] ;
} reinterpret ; 

reinterpret.byte[MSB] = 0x80u ;
reinterpret.byte[LSB] = 0xFFu ;

short word = reinterpret.word ;

https://onlinegdb.com/Byth1N3yr

0 голосов
/ 22 июня 2019

Если 0x01 - это MSB, а 0x02 - это LSB, тогда unsigned short foo = 0x01 << 8 | 0x02; будет достаточно. Однако это будет означать, что unsigned short является как минимум 16-разрядным (это зависит от реализации, ищите stdint.h для фиксированного размера)

...