c 从缓冲区读取 4 字节小端序号的代码

c Code that reads a 4 byte little endian number from a buffer

我遇到了这段现有的 C 代码。我很难理解它。

我假设将缓冲区中传递的 4 字节无符号值(以小端格式)读取到类型 "long" 的变量中。

此代码 运行s 在 64 位字大小的小端 x86 机器上 - 其中 sizeof(long) 是 8 个字节。 我的猜测是这段代码也打算 运行 在 32 位 x86 机器上 - 因此为了存储来自四字节输入数据的值,使用 long 类型的变量而不是 int。

我有一些疑问,并在代码中添加了注释以表达我理解或不理解的内容:-)

请根据该上下文回答以下问题

void read_Value_From_Four_Byte_Buff( char*input)
{
    /* use long so on 32 bit machine, can still accommodate 4 bytes */ 
    long intValueOfInput;  

    /* Bitwise and of input buffer's byte 0 with 0xFF gives MSB or LSB ?*/
    /* This code seems to assume that assignment will store in rightmost byte - is that true on a x86 machine ?*/
    intValueOfInput =  0xFF & input[0];

    /*left shift byte-1 eight times, bitwise "or" places in 2nd byte frm right*/
    intValueOfInput |= ((0xFF & input[1]) << 8);

    /* similar left shift in mult. of 8 and bitwise "or" for next two bytes */
    intValueOfInput |= ((0xFF & input[2]) << 16);
    intValueOfInput |= ((0xFF & input[3]) << 24);

}

我的问题

1) 输入缓冲区应在 "Little endian" 中。但是从代码来看,这里的假设是它以字节 0 = MSB、字节 1、字节 2、字节 3 = LSB 的形式读入。我这么认为是因为代码从字节 0 开始读取字节,随后的字节(从 1 开始)在左移后放置在目标变量中。是这样还是我理解错了?

2) 我觉得这是一种复杂的做事方式 - 是否有更简单的替代方法来将值从 4 字节缓冲区复制到长变量中?

3) 假设 "that this code will run on a 64 bit machine" 是否会影响我是否可以轻松地执行此操作?我的意思是所有这些麻烦都让它与字长无关(我现在假设它与字长无关 - 虽然不确定)?

多谢指教:-)

从代码来看,字节0是LSB,字节3是MSB。但是有一些错别字。行应该是

intValueOfInput |= ((0xFF & input[2]) << 16);
intValueOfInput |= ((0xFF & input[3]) << 24);

您可以通过删除 0xFF 但在参数类型中使用类型 "unsigned char" 来缩短代码。

为了缩短代码,您可以这样做:

long intValueOfInput = 0;
for (int i = 0, shift = 0; i < 4; i++, shift += 8)
    intValueOfInput |= ((unsigned char)input[i]) << shift;
  1. 你搞反了。当您左移时,您将投入更多有效位。所以 (0xFF & input[3]) << 24) 将字节 3 放入 MSB。

  2. 这是标准 C 中的做法。POSIX 具有将网络字节顺序转换为本机 32 位整数的函数 ntohl(),因此此通常用于 Unix/Linux 应用程序。

  3. 这在 64 位机器上不会完全相同,除非您使用 unsigned long 而不是 long。按照目前的写法,input[3] 的最高位将被放入结果的符号位(假设是二进制补码机),因此您可以获得负结果。如果long是64位,所有结果都是正数

  1. 您使用的代码确实将输入缓冲区视为小端。看看它是如何获取缓冲区的第一个字节并将其分配给变量而不进行任何移位的。如果第一个字节增加 1,则结果的值增加 1,因此它是 least-significant 字节(LSB)。 Left-shifting 使一个字节更重要,而不是更少。 Left-shifting 乘以 8 通常等于乘以 256。
  2. 我不认为你可以得到比这更简单的方法,除非你使用外部函数,或者对该代码所在的机器做出假设 运行,或者调用未定义的行为。在大多数情况下,只写 uint32_t x = *(uint32_t *)input; 就可以了,但这假设你的机器是小端,我认为根据 C 标准它可能是未定义的行为。
  3. 不,运行 在 64 位机器上不是问题。我建议使用 uint32_tint32_t 之类的类型,这样可以更轻松地推断您的代码是否适用于不同的体系结构。您只需包含 C99 中的 stdint.h header 即可使用这些类型。

此函数最后一行的 right-hand 端可能显示 undefined behavior,具体取决于输入中的数据:

((0xFF & input[3]) << 24)

问题是 (0xFF & input[3]) 将是一个带符号的 int(因为整数提升)。 int 可能是 32 位的,并且您将它向左移动太远以至于结果值可能无法在 int 中表示。 C standard 说这是未定义的行为,你真的应该尽量避免这种情况,因为它给了编译器做任何它想做的事情的许可,你将无法预测结果。

一个解决方案是在移动它之前使用转换将它从 int 转换为 uint32_t

最后,变量 intValueOfInput 被写入但从未使用过。你不应该 return 它或将它存储在某个地方吗?

考虑到所有这些,我会像这样重写函数:

uint32_t read_value_from_four_byte_buff(char * input)
{
    uint32_t x;
    x = 0xFF & input[0];
    x |= (0xFF & input[1]) << 8;
    x |= (0xFF & input[2]) << 16;
    x |= (uint32_t)(0xFF & input[3]) << 24;
    return x;
}