19

我一直在开发一个遗留的 C++ 应用程序,并且绝对超出了我的舒适区(一件好事)。我想知道是否有人愿意给我一些指示(双关语)。

我需要将 unsigned char 数组中的 2 个字节转换为 unsigned short。字节是连续的。

例如,我正在尝试做的事情:

我从套接字接收一个字符串并将其放在一个无符号字符数组中。我可以忽略第一个字节,然后接下来的 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])
4

11 回答 11

24

好吧,您正在将 char 扩大为一个短值。您想要的是将两个字节解释为一个短字节。static_cast不能从unsigned char*to 转换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];
于 2008-11-19T02:24:51.853 回答
4

这可能远低于您关心的内容,但请记住,您可以轻松获得未对齐的访问权限。x86 是宽容的,未对齐访问导致的中止将在内部被捕获,并最终得到一个值的副本和返回,因此您的应用程序不会知道任何不同(尽管它比对齐访问慢得多)。但是,如果此代码将在非 x86 上运行(您没有提到目标平台,所以我假设 x86 桌面 Windows),那么这样做会导致处理器数据中止,您将不得不手动复制在尝试强制转换之前将数据传输到对齐的地址。

简而言之,如果您要经常进行这种访问,您可能会考虑对代码进行调整,以免出现未对齐的读取,并且您会看到性能优势。

于 2008-11-19T02:15:11.403 回答
3
unsigned short myShort = *(unsigned short *)&packetBuffer[1];
于 2008-11-19T02:28:05.810 回答
3

上面的位移有一个错误:

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];

你必须小心指针,优化器会咬你,还有内存对齐和一长串其他问题。是的,做对了会更快,做错了错误会持续很长时间,并在最不希望的时候发生攻击。

假设你很懒,想在 8 位数组上做一些 16 位数学运算。(小端)

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. 更改类型时使用一个或多个函数。
于 2008-11-20T23:39:17.973 回答
2

您不应该将 unsigned char 指针转换为 unsigned short 指针(就此而言,从较小数据类型的指针转​​换为较大数据类型)。这是因为假定地址将正确对齐。更好的方法是将字节转换为真正的无符号短对象,或将 memcpy 转换为无符号短数组。

毫无疑问,您可以调整编译器设置来绕过这个限制,但这是一个非常微妙的事情,如果代码被传递和重用,将来会破坏。

于 2008-11-19T02:18:17.920 回答
2

也许这是一个非常晚的解决方案,但我只想与您分享。当你想转换原语或其他类型时,你可以使用联合。见下文:

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);
于 2013-05-21T14:50:54.813 回答
1

static cast 有不同的语法,加上你需要使用指针,你想要做的是:

unsigned short *myShort = static_cast<unsigned short*>(&packetBuffer[1]);
于 2008-11-19T02:08:41.200 回答
0

没有人看到输入是一个字符串!

/* 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;

这也避免了大多数其他解决方案在某些平台上可能存在的对齐问题。注意 short 至少是两个字节。如果您尝试取消引用不是 2 字节对齐的短指针(或者系统上的 sizeof(short) 是什么),大多数系统都会给您一个内存错误!

于 2008-11-19T03:41:08.160 回答
0
char packetBuffer[] = {1, 2, 3};
unsigned short myShort = * reinterpret_cast<unsigned short*>(&packetBuffer[1]);

我(不得不)一直这样做。大端是一个明显的问题。当机器不喜欢未对齐的读取时,真正会得到的是不正确的数据!(和写)。

您可能想编写一个测试转换和一个断言来查看它是否正确读取。因此,当在大端机器或更重要的是不喜欢未对齐读取的机器上运行时,将发生断言错误,而不是奇怪的难以追踪的“错误”;)

于 2008-11-19T05:59:48.737 回答
0

在 Windows 上,您可以使用:

unsigned short i = MAKEWORD(lowbyte,hibyte);
于 2008-11-21T11:33:20.340 回答
0

我意识到这是一个旧线程,我不能说我尝试了这里提出的每一个建议。我只是让自己对 mfc 感到满意,我正在寻找一种将 uint 转换为两个字节的方法,然后再返回到套接字的另一端。

您可以在网上找到很多位移示例,但它们似乎都没有真正起作用。很多例子似乎过于复杂。我的意思是我们只是在谈论从一个 uint 中获取 2 个字节,通过网络发送它们,然后将它们插回到另一端的 uint 中,对吧?

这是我最终想出的解决方案:

类字节转换器
{
上市:
 static void uIntToBytes(unsigned int theUint, char* bytes)
  {
   无符号整数 tInt = theUint;

   无效 *uintConverter = &tInt;
   char *theBytes = (char*)uintConverter;

   字节[0] = theBytes[0];
   字节[1] = theBytes[1];
  }
 静态无符号整数 bytesToUint(char *bytes)
  {
   unsigned theUint = 0;

   无效 *uintConverter = &theUint;
   char *thebytes = (char*)uintConverter;

   thebytes[0] = 字节[0];
   thebytes[1] = 字节[1];

   返回 Uint;
  }
};

像这样使用:

无符号整数;
字符字节[2];
CString 消息;
ByteConverter::uIntToBytes(65000,bytes); theUint = ByteConverter::bytesToUint(bytes);
msg.Format(_T("theUint = %d"), theUint); AfxMessageBox(msg, MB_ICONINFORMATION | MB_OK);

希望这可以帮助某人。

于 2010-01-23T20:37:08.140 回答