将十六进制字符串转换为 signed int 会在不同平台上产生不同的值

Casting hex string to signed int results in different values in different platforms

我正在处理一个我想成为多平台的程序中的边缘情况。这是问题的摘录:

#include <stdio.h>
#include <string.h>

void print_bits(size_t const size, void const * const ptr){
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;

    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
        }
    }
    puts("");
}

int main() {

char* ascii = "0x80000000";
int myint = strtol(ascii, NULL, 16);

printf("%s to signed int is %d and bits are:\t", ascii, myint);
print_bits(sizeof myint, &myint);

return 0;
}

所以当我在 Linux 上使用 GCC 编译时,我得到了这个输出:

0x80000000 to signed int is -2147483648 and bits are:   10000000000000000000000000000000

在 Windows 上,使用 MSVC 和 MinGW 我得到:

0x80000000 to signed int is 2147483647 and bits are:    01111111111111111111111111111111

我认为 GCC 输出了正确的预期值。我的问题是,这种差异从何而来以及如何确保在所有编译器上我都能得到正确的结果?

更新

这段代码背后的原因是,我必须检查 HEX 值的 MSB(第 31 位)是 0 还是 1。然后,我必须得到接下来 7 位的无符号整数值(# 30 至 #24) 结果(在 0x80000000 的情况下,这 7 位应导致 0:

    int msb_is_set = myint & 1;
    uint8_t next_7_bits;

next_7_bits = myint >> 24; //fine on GCC, outputs 0 for the next 7 bits
#ifdef WIN32 //If I do not do this, next_7_bit will be 127 on Windows instead of 0
    if(msb_is_set )
        next_7_bits = myint >> 1;
#endif

P.S。这是在同一台机器上(i5 64 位)

您在这里处理不同的数据模型。

Windows 64使用LLP64,也就是说只有long long,指针是64位的。 随着strtol转为long,转为32位的值,32位有符号整数中的0x80000000为负数。

Linux 64 使用 LP64,所以 longlong long 和指针是 64 位的。 我猜你现在明白这里发生了什么 ;)


感谢评论,我意识到我最初的回答是错误的。不同的结果确实与这些平台上的不同模型有关。 But:在 LP64 模型的情况下,您可以转换为无法保存值的有符号类型,这是实现定义的。 int 在两个平台上都是 32 位的,而 32 位的 int 不能容纳 0x80000000。所以正确的答案是:你不应该期望你的代码在 Linux64 上有任何结果。在 Win64 上,由于 long 只有 32 位,strtol() 正确地 returns LONG_MAX0x80000000,恰好只比您输入的小一个。

int myint = strtol(ascii, NULL, 16);

strtol 是 'string to long',不是字符串到整数。

此外,您可能希望 0x800000000 是一个无符号长整数。

您可能会发现,在(那个版本的)Linux 上,int 是 64 位的,而在(那个版本的)Windo3ws 上,int 是 32 位的。

不要这样做:

#ifdef __GCC__ 

因为编译器开关可能会改变工作方式。最好做类似的事情:

在某些 header 某处:

#ifdef __GCC__
#define FEATURE_SHIFT_RIGHT_24
#endif
#ifdef __MSVC__
#define FEATURE_SHIFT_RIGHT_1
#endif

然后在你的主代码中:

#ifdef FEATURE_SHIFT_RIGHT_24
next_7_bits = myint >> 24;
#endif
#ifdef FEATURE_SHIFT_RIGHT_1
   if(msb_is_set )
     next_7_bits = myint >> 1;
#endif

您的代码应该处理实现细节,header 应该检查哪个编译器需要哪个实现。

这将所需的代码与检测此编译器所需的方法分开。在您的 header 中,您可以对编译器功能进行更复杂的检测。

例如

#ifdef __GCC__ && __GCCVERION__ > 1.23

等等

这是关于您的更新。虽然我不确定你的意图是什么,但让我们先指出一些错误:

#ifdef WIN32

目标 win32 时始终定义的宏是 _WIN32,而不是 WIN32

然后你有另一个 #ifdef 检查 GCC,但这不会做你期望的:GCC 也存在于 win32 上,它使用与 MSVC 相同的数据模型。 IOW,你可以同时定义 __GCC___WIN32.

你说你想知道是否设置了MSB。然后确保将您的字符串转换为 unsigned int 并直接检查此位:

#include <limits.h>
// [...]
unsigned int myint = strtoul(ascii, NULL, 16); // <- strtoul(), not strtol()!
unsigned int msb = 1U << (sizeof(unsigned int) * CHAR_BIT - 1);
if (myint & msb)
{
    // msb is set
}

顺便说一句,请参阅 this answer 以了解一种真正可移植的方法来获取整数类型的位数。 sizeof() * CHAR_BIT 将在具有 填充位 .

的平台上失败