Побитовая распаковка с использованием подписанных данных

#c #c #bit-manipulation #bitwise-operators

#c #c #манипулирование битами #побитовые операторы

Вопрос:

Я некоторое время пытался упаковать и распаковать некоторые символы в целое число. Хотя есть некоторые темы, связанные с этим вопросом, моя проблема связана с подписанным сдвигом. Я не понимаю «трюк» для распаковки подписанного значения, т.е.:

 char c1 = -119;
char c2 = 26;

// pack
int packed = (unsigned char)c1 | (c2 << 8);
// unpack
c1 = packed >> 0;
c2 = packed >> 8;

// printf(c1, c2) -> Unpacked data: -119 | 26
  

Это работает, как и ожидалось, но когда я пытаюсь упаковать больше данных, т.е:

 char c0 = -42;
char c1 = -119;
char c2 = 26;

// pack
int packed = (unsigned char)c0 | (unsigned char)(c1 << 8) | (c2 << 16);
// unpack
c0 = packed >> 0;
c1 = packed >> 8;
c2 = packed >> 16;

// printf -> Unpacked data: -42 | 0 | 26
  

значение c1 пропущено. Я предполагаю, что это связано с тем, что бит знака смещен в позицию старшего порядка.

Как я могу вернуть значение c1?

Заранее спасибо.

Ответ №1:

Вы выполняете приведение c1 к unsigned char после смещения его из диапазона этого типа, поэтому результат приведения равен нулю. Вы должны выполнить приведение перед переносом:

 int packed = (unsigned char)c0 | ((unsigned char)c1 << 8) | (c2 << 16);
  

Комментарии:

1. Хорошее объяснение 🙂 Сомнениям нет места. Спасибо вам!

Ответ №2:

 (unsigned char)(c1 << 8)
  

Это приведет

  • смещение неправильного (расширенного по знаку) значения
  • обрезать результат до 8 бит (получая 0)

Вам ничего этого не нужно, поэтому вы должны использовать ((unsigned char)c1 << 8) .

Комментарии:

1. Неверно. Приведение signed к unsigned никогда не является неопределенным поведением, независимо от того, насколько широки или узки типы. (В отличие от преобразования unsigned в signed .). Код по-прежнему неправильный, но это не UB.

Ответ №3:

Некоторые int s составляют 16 бит. Чтобы этот код был переносимым, используйте int32_t . Правильный способ добиться этого (если немного параноидальный):

 int32_t packed = ((uint8_t)c0) | (((uint8_t)c1)<<8) | (((uint8_t)c2) << 16);
  

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