C++:如何将数组中的2个字节转换为无符号短整型

19

我一直在处理一个老旧的C++应用程序,这让我感到非常不舒服(但也是好事)。我想知道是否有人能够友善地给我一些指导(双关语)。

我需要将一个无符号字符数组中的2个字节转换为一个无符号short类型。这两个字节是连续的。

举个例子,我从socket接收到一个字符串,并将其放入一个无符号字符数组中。我可以忽略第一个字节,然后将下一个2个字节转换为一个unsigned char类型。这只在Windows平台上执行,所以没有大小端问题(至少目前我不知道)。

以下是我目前的代码(显然无法工作):

//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])

现在,这个人一定会怎么看我们C++程序员。每个人都有自己的“正确”解决方案 :D - Johannes Schaub - litb
他可能在谈论我们C++程序员是一群狡猾的人,随心所欲地改变规则。咪哈哈哈。 - baash05
输入是否包含值为'0'-'9'的字符串,或者包含值为0-255的字节?文档中说是字符串,但在这种情况下乘以256没有意义。 - Mark Ransom
我怀疑它包含了二进制数字1到9。 - Johannes Schaub - litb
11个回答

24

你正在将char扩展为short值。你想要的是将两个字节解释为short类型。static_cast无法将unsigned char*转换为unsigned short*。你需要先将其转换为void*,然后再转换为unsigned short*

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

现在,您可以取消引用 p 并获取 short 值。但是这种方法的问题在于,您从 unsigned char* 强制转换为 void*,然后再转换为某个不同类型。标准不能保证地址保持不变(此外,取消引用该指针将导致未定义行为)。更好的方法是使用位移,它始终可行:

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

2
移位部分是处理所有硬件类型的可靠方法。但偏移量为0和1,而不是1和2 - 我马上进行编辑。 - Jonathan Leffler
3
这段话(以及其他答案)假定了字节序是大端序,我认为。 - Jonathan Leffler
2
Jonathan,你的编辑是错误的。他想要其中包含2和3,而不是1和2。 - Johannes Schaub - litb

4
这可能远远低于您所关心的范围,但请记住,这样做很容易导致未对齐的访问。x86 是宽容的,并且未对齐访问引起的中止将在内部捕获,并导致值的复制和返回,因此您的应用程序不会有任何区别(尽管比对齐访问慢得多)。 但是,如果该代码将在非 x86 平台上运行(您没有提及目标平台,因此我假设为 x86 桌面 Windows),则执行此操作将导致处理器数据异常,并且必须在尝试转换之前手动将数据复制到对齐地址。

简而言之,如果您将经常进行此访问,则可以调整代码以避免未对齐读取,并获得性能优势。


你不需要复制;可以使用位移操作。 - Jonathan Leffler
@Jonathan:是的,但它仍然需要将其分配到另一个变量中,这是一份副本。 - ctacke

3
unsigned short myShort = *(unsigned short *)&packetBuffer[1];

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

在处理指针、内存对齐和其他一系列问题时,你必须小心谨慎,否则优化器会给你带来麻烦。如果正确操作,速度会更快,但如果出错,可能会导致bug长时间存在,并在最不希望的时候出现。

例如,假设你想在一个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);

没有保证完全无bug的编译器能够生成您所期望的代码。发送到do_something_with()函数的字节数组b可能永远不会被*s操作修改。上面的代码中没有任何指定它应该这样做的内容。如果您不优化您的代码,那么您可能永远不会看到这个问题(直到有人优化或更改编译器或编译器版本)。如果您使用调试器,您可能永远不会看到这个问题(直到为时已晚)。
编译器看不到s和b之间的关系,它们是两个完全独立的项目。优化器可能选择不将*s写回内存,因为它看到*s有多个操作,所以它可以将该值保存在寄存器中,并仅在最后(如果有必要)将其保存到内存中。
解决上述指针问题的基本方法有三种:
  1. s声明为volatile。
  2. 使用联合。
  3. 在更改类型时使用一个或多个函数。

1
字符值首先被转换为整数(提升),然后进行移位。如果左侧和右侧都是字符,则会遇到该问题,但不会被转换为零。 - Johannes Schaub - litb
操作数必须是整数或枚举类型,并执行整数提升。结果的类型与提升后的左操作数相同。如果右操作数为负数或大于等于提升后的左操作数的位长度,则行为未定义。 - Johannes Schaub - litb

2

不应该将无符号字符指针强制转换为无符号短整型指针(或者从小数据类型的指针转换为大数据类型的指针)。这是因为假定地址会正确对齐。更好的方法是将字节移位到真正的无符号短整型对象,或者使用memcpy复制到无符号短整型数组中。

毫无疑问,您可以调整编译器设置来避免此限制,但这是一个非常微妙的问题,如果代码被传递和重用,将来可能会出现问题。


2
也许这是一个很晚的解决方案,但我想与您分享。当您想要转换基元类型或其他类型时,可以使用union。请参见以下内容:
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的short值。

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

1

static_cast 有不同的语法,而且你需要使用指针,你想要做的是:

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

这是错误的!它无法编译。虽然我不建议这样做,但至少reinterpret_cast更好一些。 - sep
事实上,static_cast 只能将标准隐式转换的相反方向强制转换,从派生类到其虚拟基类之一的独占转换。 unsigned short * p; unsigned char * c = p; 这样做是行不通的。 - Johannes Schaub - litb
注意对齐问题。 - Martin York

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

我经常需要这样做。大端序是一个明显的问题。真正会让你犯难的是当机器不喜欢未对齐读取时,数据就会出现错误!(和写入)。

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


0

我意识到这是一个旧的线程,我不能说我尝试了这里提出的每个建议。我只是让自己熟悉MFC,并且我正在寻找一种将uint转换为两个字节并在套接字的另一端再次转换的方法。

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

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

类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);

希望这能帮助到某些人。


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 字节对齐的 short 指针(或者在您的系统上 sizeof(short) 是什么),大多数系统将会给出内存错误!

这不是一个字符串 - 字节在代码集中不一定表示数字。 - Jonathan Leffler
1
我引用:“packetBuffer是一个无符号字符数组,包含字符串”123456789“”。 - Martin York
1
我接收一个字符串从套接字并将其放置在无符号字符数组中。 - Martin York
好的 - 它是一个字符串;它比我意识到的更奇怪;抱歉。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接