在 c 中将字节转换为整数时出现奇怪的值

Strange values when converting bytes to integer in c

看看这段代码:

#include <stdio.h>
#include <stdlib.h>

int byteToInt(char *bytes) {
    int32_t v = 
        (bytes[0]      ) +
        (bytes[1] << 8 ) +
        (bytes[2] << 16) +
        (bytes[3] << 24);
    return v;
}

int main() {
    char b1[] = {0xec, 0x51, 0x04, 0x00};
    char b2[] = {0x0c, 0x0c, 0x00, 0x00};

    printf("%d\n", byteToInt(b1));
    printf("%d\n", byteToInt(b2));
    printf("%d\n", *(uint32_t *)b1);
    printf("%d\n", *(uint32_t *)b2);

    return 0;
}

{0xec, 0x51, 0x04, 0x00} 等于 283116,但是当我使用 byteToInt 函数时,它 returns,出于某种原因,282860。有一些字节数组会导致类似的麻烦。我意识到值总是被 256 弄错了。不过,大多数情况下都没有任何问题 - 看看 b2,它被计算为 3084,这是正确的。铸造方法在这些情况下非常有效,但我想知道所描述的问题发生了什么。有人可以给我解释一下吗?

也许char是有符号类型(它是实现定义的),(int)(char)(0xec)-20,而(int)(unsigned char)(0xec)236

尝试使用unsigned charuint32_t

uint32_t byteToInt(unsigned char *bytes) {
    uint32_t v =
        ((uint32_t)bytes[0]) +
        ((uint32_t)bytes[1] << 8) +
        ((uint32_t)bytes[2] << 16) +
        ((uint32_t)bytes[3] << 24);
    return v;
}

int main() {
    unsigned char b1[] = { 0xec, 0x51, 0x04, 0x00 };
    unsigned char b2[] = { 0x0c, 0x0c, 0x00, 0x00 };

    printf("%u\n", byteToInt(b1));     // 'u' for unsigned
    printf("%u\n", byteToInt(b2));
    //printf("%u\n", *(uint32_t *)b1); // undefined behavior
    //printf("%u\n", *(uint32_t *)b2); // ditto

    return 0;
}

请注意,在最后两个 printf 中重新解释内存内容是未定义的行为(尽管在实践中经常有效)。

顺便说一句,根据标准,移动带符号的负值是未定义的:

The result of E1 << E2 is E1 left-shifted E2 bit positions; ... If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

此代码存在几个潜在问题。首先是编译器依赖于 char 类型是 8 位、16 位还是 32 位。当您对字符类型进行移位操作时,它可能会丢失值的位"off the end"。

在移动和添加它们之前先将值转换为 32 位类型更安全。例如:

unsigned long v = 
    ((unsigned long)bytes[0]      ) +
    ((unsigned long)bytes[1] << 8 ) +
    ((unsigned long)bytes[2] << 16) +
    ((unsigned long)bytes[3] << 24);

您对 int32_t 的使用也取决于编译器。如果没记错的话,那是 Windows 对 int 的特定重新分类。 "int" 本身依赖于编译器,旧的编译器可能将它作为 16 位值,因为标准规定它应该是您正在使用的机器上的一个字的大小。使用 "long" 而不是 "int" 可以保证 32 位值。

此外,我在示例中使用了 "unsigned long",因为我认为您不想在这种情况下处理负数。在二进制表示中,负数具有最高位集 (0x8000000)。

如果您确实想使用负数,那么类型应该是 "long",尽管这会在将正值字节添加到负值最大字节时打开一个不同的蠕虫罐。在你想处理负数的情况下,你应该做一个完全不同的转换,剥离高字节的高位,做加法,然后,如果高位被设置,使值负(v = -v;), 然后你需要减去1因为负数的表示(这可能不在这个问题的范围内。)

修改后的代码为:

#include <stdio.h>
#include <stdlib.h>

unsigned long byteToInt(char *bytes) {
    unsigned long v = 
        ((unsigned long)bytes[0]      ) +
        ((unsigned long)bytes[1] << 8 ) +
        ((unsigned long)bytes[2] << 16) +
        ((unsigned long)bytes[3] << 24);
    return v;
}

int main() {
    char b1[] = {0xec, 0x51, 0x04, 0x00};
    char b2[] = {0x0c, 0x0c, 0x00, 0x00};

    printf("%d\n", byteToInt(b1));
    printf("%d\n", byteToInt(b2));
    printf("%d\n", *(unsigned long *)b1);
    printf("%d\n", *(unsigned long *)b2);

    return 0;
}