【问题标题】:Strange values when converting bytes to integer in c在c中将字节转换为整数时出现奇怪的值
【发布时间】:2016-10-04 02:47:41
【问题描述】:

看看这段代码:

#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 函数时,由于某种原因,它返回282860。有一些字节数组会引起类似的麻烦。我意识到这个值总是被 256 弄错。不过,大多数情况下都没有任何问题 - 看看b2,它被计算为3084,这是正确的。铸造方法在这些情况下非常有效,但我想知道所描述的问题发生了什么。有人可以向我解释一下吗?

【问题讨论】:

  • char 数组转换为uint32_t * 是未定义的行为,因为它违反了strict aliasing。这是在具有严格对齐要求的机器上获得SIGBUS 的好方法。

标签: c algorithm hex decimal


【解决方案1】:

也许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;
}

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

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

E1 &lt;&lt; E2 的结果是E1 左移E2 位位置; ... 如果E1 有一个签名 类型和非负值,E1 × 2E2 在结果类型中是可表示的,那么就是 结果值;否则,行为未定义

【讨论】:

  • 没错,在我的情况下它是签名的字符,你的代码完美无缺。谢谢!
  • @solusipse 请记住,转换有符号值可能很脆弱。 (我刚刚更新了答案。)
  • 在char为8位、int为32位的合理假设下,bytes[3] &lt;&lt; 24在设置bytes[3]的最高位时不会引起UB吗? bytes[3] 将在班次前提升为 intbytes[3] &lt;&lt; 24 将导致溢出。 ((uint32_t)bytes[3]) &lt;&lt; 24 是正确的,避免了 UB。
  • @solusipse 我添加了一个小的更正以防止升级到int,感谢@PaulHankin
【解决方案2】:

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

在移位和添加它们之前先将值转换为 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    • 2014-08-28
    • 1970-01-01
    相关资源
    最近更新 更多