C ++: как привести 2 байта в массиве к беззнаковому короткому - PullRequest
18 голосов
/ 19 ноября 2008

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

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

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

Я получаю строку из сокета и помещаю ее в массив без знака. Я могу игнорировать первый байт, а затем следующие 2 байта должны быть преобразованы в беззнаковый символ. Это будет только на окнах, поэтому нет проблем 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])

Ответы [ 11 ]

22 голосов
/ 19 ноября 2008

Ну, вы расширяете символ в короткое значение. Вы хотите интерпретировать два байта как короткий. 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 *, а затем в другой тип. Стандарт не гарантирует, что адрес останется прежним (и, кроме того, разыменование этого указателя будет неопределенным поведением). Лучше всего использовать сдвиг битов, который всегда будет работать:

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];
4 голосов
/ 19 ноября 2008

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

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

3 голосов
/ 21 ноября 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. Используйте функцию или функции при смене типа.
2 голосов
/ 21 мая 2013

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

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);
2 голосов
/ 19 ноября 2008
unsigned short myShort = *(unsigned short *)&packetBuffer[1];
2 голосов
/ 19 ноября 2008

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

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

1 голос
/ 19 ноября 2008

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

unsigned short *myShort = static_cast<unsigned short*>(&packetBuffer[1]);
0 голосов
/ 23 января 2010

Я понимаю, что это старая ветка, и я не могу сказать, что перепробовал каждое предложение, сделанное здесь. Я просто чувствую себя комфортно с 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;<br>
ByteConverter::uIntToBytes(65000,bytes);
theUint = ByteConverter::bytesToUint(bytes);<br>
msg.Format(_T("theUint = %d"), theUint);
AfxMessageBox(msg, MB_ICONINFORMATION | MB_OK);

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

0 голосов
/ 21 ноября 2008

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

unsigned short i = MAKEWORD(lowbyte,hibyte);
0 голосов
/ 19 ноября 2008
char packetBuffer[] = {1, 2, 3};
unsigned short myShort = * reinterpret_cast<unsigned short*>(&packetBuffer[1]);

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

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

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