С++: как привести 2 байта в массиве к беззнаковому шорту

Я работаю над устаревшим приложением C++ и определенно нахожусь за пределами своей зоны комфорта (хорошо). Мне было интересно, не будет ли кто-нибудь так любезен, чтобы дать мне несколько советов (каламбур).

Мне нужно преобразовать 2 байта в беззнаковый массив символов в беззнаковый шорт. Байты идут подряд.

Для примера того, что я пытаюсь сделать:

Я получаю строку из сокета и помещаю ее в массив символов без знака. Я могу игнорировать первый байт, а затем следующие 2 байта должны быть преобразованы в беззнаковый символ. Это будет только в Windows, поэтому проблем с Big/Little Endian (о которых я знаю) не будет.

Вот что у меня сейчас (не работает явно):

//packetBuffer is an unsigned char array containing the string "123456789" for testing
//I need to convert bytes 2 and 3 into the short, 2 being the most significant byte
//so I would expect to get 515 (2*256 + 3) instead all the code I have tried gives me
//either errors or 2 (only converting one byte
unsigned short myShort;
myShort = static_cast<unsigned_short>(packetBuffer[1])

person user38784    schedule 19.11.2008    source источник
comment
Теперь, что этот парень должен думать о нас, программистах на C++. у каждого есть другое правильное решение :D   -  person Johannes Schaub - litb    schedule 19.11.2008
comment
ну, он мог бы говорить о том, что мы, c++ ppl, хитрые люди, и мы меняем правила, как нам нравится. Му ха хааа.   -  person baash05    schedule 19.11.2008
comment
Содержит ли ввод строку со значениями от «0» до «9» или содержит байты со значениями от 0 до 255? В документах написано строка, но в этом случае нет смысла умножать на 256.   -  person Mark Ransom    schedule 19.11.2008
comment
я подозреваю, что он содержит двоичные числа от 1 до 9.   -  person Johannes Schaub - litb    schedule 19.11.2008


Ответы (10)


Ну, вы расширяете char до короткого значения. Что вы хотите, так это интерпретировать два байта как короткие. static_cast нельзя использовать с unsigned char* на unsigned short*. Вы должны привести к void*, затем к unsigned short*:

unsigned short *p = static_cast<unsigned short*>(static_cast<void*>(&packetBuffer[1]));

Теперь вы можете разыменовать p и получить короткое значение. Но проблема с этим подходом заключается в том, что вы приводите тип unsigned char* к void*, а затем к другому типу. Стандарт не гарантирует, что адрес останется прежним (и, кроме того, разыменование этого указателя будет поведением undefined). Лучшим подходом является использование битового сдвига, который всегда будет работать:

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];
person Johannes Schaub - litb    schedule 19.11.2008
comment
Часть сдвига - это правильный способ надежно справиться с этим на всех типах оборудования. Но смещения 0 и 1, а не 1 и 2 - я сейчас отредактирую. - person Jonathan Leffler; 19.11.2008
comment
И этот (и другие ответы) предполагают порядок байтов - я думаю, большой порядок байтов. - person Jonathan Leffler; 19.11.2008
comment
Джонатан, ваше редактирование неверно. он хотел, чтобы в нем было 2 и 3, а не 1 и 2. - person Johannes Schaub - litb; 21.11.2008

Это, вероятно, намного ниже того, что вас волнует, но имейте в виду, что вы можете легко получить невыровненный доступ, делая это. x86 прощает ошибки, и прерывание, которое вызывает невыровненный доступ, будет перехвачено внутри и закончится копированием и возвратом значения, поэтому ваше приложение не будет знать ничего другого (хотя это значительно медленнее, чем выровненный доступ). Если, однако, этот код будет работать на не-x86 (вы не упомянули целевую платформу, поэтому я предполагаю x86 настольную Windows), то выполнение этого приведет к прерыванию данных процессора, и вам придется вручную копировать данные на выровненный адрес перед попыткой его приведения.

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

person ctacke    schedule 19.11.2008
comment
Вам не нужно копировать; вместо этого вы можете выполнять операции сдвига битов. - person Jonathan Leffler; 19.11.2008
comment
@Jonathan: да, но по-прежнему требуется присваивание другой переменной, которая является копией. - person ctacke; 19.11.2008

Битовый сдвиг выше имеет ошибку:

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];

если packetBuffer находится в байтах (шириной 8 бит), то приведенный выше сдвиг может и превратит packetBuffer в ноль, оставив вам только packetBuffer[2];

Несмотря на это, это по-прежнему предпочтительнее указателей. Чтобы избежать вышеупомянутой проблемы, я трачу несколько строк кода (кроме совершенно буквальной нулевой оптимизации), это приводит к тому же машинному коду:

unsigned short p;
p = packetBuffer[1]; p <<= 8; p |= packetBuffer[2];

Или, чтобы сэкономить несколько тактов и не сдвигать биты с конца:

unsigned short p;
p = (((unsigned short)packetBuffer[1])<<8) | packetBuffer[2];

Вы должны быть осторожны с указателями, оптимизатор вас укусит, а также с выравниванием памяти и длинным списком других проблем. Да, если все сделано правильно, это быстрее, если неправильно, ошибка может задержаться на долгое время и ударить в самый неподходящий момент.

Скажем, вы были ленивы и хотели выполнить 16-битную математику в 8-битном массиве. (маленький порядок байтов)

unsigned short *s;
unsigned char b[10];

s=(unsigned short *)&b[0];

if(b[0]&7)
{
   *s = *s+8;
   *s &= ~7;
}

do_something_With(b);

*s=*s+8;

do_something_With(b);

*s=*s+8;

do_something_With(b);

Нет никакой гарантии, что совершенно свободный от ошибок компилятор создаст код, который вы ожидаете. Массив байтов b, отправленный в функцию do_something_with(), никогда не может быть изменен операциями *s. Ничто в приведенном выше коде не говорит, что так и должно быть. Если вы не оптимизируете свой код, вы можете никогда не столкнуться с этой проблемой (пока кто-нибудь не оптимизирует или не изменит компиляторы или версии компиляторов). Если вы используете отладчик, вы можете никогда не увидеть эту проблему (пока не станет слишком поздно).

Компилятор не видит связи между s и b, это два совершенно разных элемента. Оптимизатор может решить не записывать *s обратно в память, потому что он видит, что *s имеет ряд операций, поэтому он может сохранить это значение в регистре и сохранить его в памяти только в конце (если вообще когда-либо).

Есть три основных способа исправить указанную выше проблему с указателем:

  1. Объявить s изменчивым.
  2. Используйте союз.
  3. Используйте функцию или функции при изменении типов.
person old_timer    schedule 20.11.2008
comment
он не превратится в ноль. значение char сначала преобразуется в int (расширяется), а затем сдвигается. если и левая, и правая стороны обуглены, то это столкнется с этой проблемой - person Johannes Schaub - litb; 21.11.2008
comment
Операнды должны быть целочисленного типа или типа перечисления, и выполняются целочисленные продвижения. Тип результата соответствует повышенному левому операнду. Поведение не определено, если правый операнд отрицателен или больше или равен длине в битах повышенного левого операнда. - person Johannes Schaub - litb; 21.11.2008

Вы не должны приводить беззнаковый указатель char к беззнаковому короткому указателю (в этом отношении от указателя меньшего типа данных к большему типу данных). Это связано с тем, что предполагается, что адрес будет выровнен правильно. Лучшим подходом является сдвиг байтов в реальный беззнаковый короткий объект или memcpy в беззнаковый короткий массив.

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

person sep    schedule 19.11.2008

Возможно, это очень позднее решение, но я просто хочу поделиться с вами. Если вы хотите преобразовать примитивы или другие типы, вы можете использовать объединение. Смотри ниже:

union CharToStruct {
    char charArray[2];
    unsigned short value;
};


short toShort(char* value){
    CharToStruct cs;
    cs.charArray[0] = value[1]; // most significant bit of short is not first bit of char array
    cs.charArray[1] = value[0];
    return cs.value;
}

Когда вы создаете массив с шестнадцатеричными значениями ниже и вызываете функцию toShort, вы получите короткое значение с 3.

char array[2]; 
array[0] = 0x00;
array[1] = 0x03;
short i = toShort(array);
cout << i << endl; // or printf("%h", i);
person ilkayaktas    schedule 21.05.2013

static cast имеет другой синтаксис, плюс вам нужно работать с указателями, что вы хотите сделать, это:

unsigned short *myShort = static_cast<unsigned short*>(&packetBuffer[1]);
person arul    schedule 19.11.2008
comment
Это не верно! Он не будет компилироваться. Хотя я бы не рекомендовал это, по крайней мере, реинтерпретировать_каст будет лучше. - person sep; 19.11.2008
comment
на самом деле, static_cast может выполнять обратное преобразование [что может делать стандартное неявное преобразование, эксклюзивное преобразование производного класса в один из его виртуальных базовых классов] unsigned short * p; беззнаковый символ * c = p; не будет работать - person Johannes Schaub - litb; 19.11.2008
comment
Остерегайтесь проблем с выравниванием. - person Martin York; 19.11.2008

Разве никто не видел, что ввод был строкой!

/* If it is a string as explicitly stated in the question.
 */
int byte1 = packetBuffer[1] - '0'; // convert 1st byte from char to number.
int byte2 = packetBuffer[2] - '0';

unsigned short result = (byte1 * 256) + byte2;

/* Alternatively if is an array of bytes.
 */
int byte1 = packetBuffer[1];
int byte2 = packetBuffer[2];

unsigned short result = (byte1 * 256) + byte2;

Это также позволяет избежать проблем с выравниванием, которые могут возникнуть в большинстве других решений на определенных платформах. Примечание. Короткое замыкание составляет не менее двух байтов. Большинство систем выдаст вам ошибку памяти, если вы попытаетесь разыменовать короткий указатель, который не выровнен по 2 байтам (или какой-либо размер sizeof(short) в вашей системе)!

person Martin York    schedule 19.11.2008
comment
Это не строка, и байты не обязательно представляют цифры в кодовом наборе. - person Jonathan Leffler; 19.11.2008
comment
Цитирую: 'packetBuffer - это массив символов без знака, содержащий строку 123456789' - person Martin York; 19.11.2008
comment
Цитирую: «Я получаю строку из сокета и помещаю ее в массив беззнаковых символов» - person Martin York; 19.11.2008
comment
OK - это строка; это более странно, чем я думал; Прости. - person Jonathan Leffler; 21.11.2008

char packetBuffer[] = {1, 2, 3};
unsigned short myShort = * reinterpret_cast<unsigned short*>(&packetBuffer[1]);

Я (пришлось) делать это все время. big endian - очевидная проблема. Что действительно принесет вам неправильные данные, когда машина не любит несогласованные чтения! (и написать).

вы можете написать тестовое приведение и утверждение, чтобы увидеть, правильно ли оно читается. Таким образом, при запуске на машине с обратным порядком байтов или, что более важно, на машине, которая не любит неправильное чтение, вместо странной трудно отслеживаемой «ошибки» произойдет ошибка утверждения;)

person Community    schedule 19.11.2008

В окнах вы можете использовать:

unsigned short i = MAKEWORD(lowbyte,hibyte);
person Richard    schedule 21.11.2008

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

В сети можно найти множество примеров сдвига битов, но ни один из них не работает. Многие примеры кажутся слишком сложными; Я имею в виду, что мы просто говорим о захвате 2 байтов из uint, отправке их по проводу и подключении их обратно к uint на другом конце, верно?

Это решение, которое я наконец придумал:

class ByteConverter
{
public:
 static void uIntToBytes(unsigned int theUint, char* bytes)
  {
   unsigned int tInt = theUint;

   void *uintConverter = &tInt;
   char *theBytes = (char*)uintConverter;

   bytes[0] = theBytes[0];
   bytes[1] = theBytes[1];
  }
 static unsigned int bytesToUint(char *bytes)
  {
   unsigned theUint = 0;

   void *uintConverter = &theUint;
   char *thebytes = (char*)uintConverter;

   thebytes[0] = bytes[0];
   thebytes[1] = bytes[1];

   return theUint;
  }
};

Используется следующим образом:

unsigned int theUint;
char bytes[2];
CString msg;
ByteConverter::uIntToBytes(65000,bytes); theUint = ByteConverter::bytesToUint(bytes);
msg.Format(_T("theUint = %d"), theUint); AfxMessageBox(msg, MB_ICONINFORMATION | MB_OK);

Надеюсь, это поможет кому-то.

person Pete    schedule 23.01.2010