【问题标题】:Casting hex string to signed int results in different values in different platforms将十六进制字符串转换为有符号整数会在不同平台上产生不同的值
【发布时间】:2017-06-05 09:26:50
【问题描述】:

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

#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

附:这是在同一台机器上(i5 64bit)

【问题讨论】:

  • "MinGW" 是 gcc 。
  • 您能解释一下您期望在所有平台上的行为,更重要的是,为什么? int 可以是 16 位以上的任意大小,long 可以是 32 位以上的任意大小,并且某些平台可能不使用 2 的补码
  • 另外,您会使用包含前导 - 符号的输入吗?
  • 注意。由于在范围内没有原型的情况下调用strtol,此代码违反了约束,您的编译器都应该对此进行诊断(如果没有,则重新考虑您的编译器开关)。 (在 C89 中,这是未定义的行为,无需诊断)
  • 对平台做出共同假设;根据 C 标准,您的 MSVC 输出是正确的,并且“gcc on linux”是实现定义的

标签: c gcc visual-c++ bit-manipulation strtol


【解决方案1】:

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

Windows 64 使用LLP64,这意味着只有long long,并且指针是64 位的。 strtol 转换为long 时,它转换为32 位值,而32 位有符号整数中的0x80000000 为负数。

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


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

【讨论】:

  • 没有未定义的行为。溢出是指算术运算的结果会产生超出范围的值,但转换不是算术运算
  • @M.M 所以它至少是定义的实现(它取决于内部表示)——它仍然禁止对结果进行任何假设。但可以肯定的是,为了正确,我会改变措辞,谢谢!
  • @FelixPalmen 我的意思是在 GCC 上,我可以通过 myint &gt;&gt; 24 将接下来的 7 位转换为 uint8_t 数据类型。在0x80000000 的情况下,它应该是 0,但在 Windows 上它是 127,我必须移动 1 才能得到 0。
  • @SaeidYazdani 现在非常不清楚你想要完成什么,而且它看起来不像是便携。但是对于检查转换为 unsigned int 的字符串的 MSB 的直接方法,请参阅我的其他答案。
【解决方案2】:
int myint = strtol(ascii, NULL, 16);

strtol 是“字符串转长”,而不是字符串转 int。

另外,您可能希望 0x800000000 为无符号长整数。

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

【讨论】:

    【解决方案3】:

    不要这样做:

    #ifdef __GCC__ 
    

    因为编译器开关可能会改变工作方式。最好这样做:

    在某处的某个标题中:

    #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
    

    您的代码应处理实现细节,并且标头应检查哪个编译器需要哪个实现。

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

    例如

    #ifdef __GCC__ && __GCCVERION__ > 1.23
    

    【讨论】:

      【解决方案4】:

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

      #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 将在具有填充位的平台上失败。

      【讨论】:

      • unsigned long 是保存strtoul结果的更好选择
      • @M.M 会的,但 OP 想知道是否设置了 unsigned int 的 MSB,至少如果我理解正确的话。
      • 好的。我把它读作专门询问第 31 位(int 可能不是 32 位)
      • @M.M 不幸的是,目前还不清楚,但要获得具体的第 31 位,在代码中会更简单......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-05
      • 2019-05-03
      • 2012-08-29
      • 2011-04-11
      • 1970-01-01
      相关资源
      最近更新 更多