【问题标题】:c++ stream negative number conversionc++流负数转换
【发布时间】:2018-08-14 10:30:57
【问题描述】:

我遇到了一个问题,C++ 试图读取一个以十六进制形式填充有符号整数并将它们解析为向量的文本文件。我使用 C++ 流到变量重定向(流 >> var),似乎没有正确解析负数 - 变量获取值 0,并且设置了流失败标志。

如果我尝试使用 strtol() 函数转换字符串,结果与预期一致。同样,如果我尝试首先将流重定向到无符号整数,然后将变量转换为有符号整数,结果再次正确并且不会报告流错误。

我在 Debian 9.1 (x64) 上使用 gcc 6.3.0,在 Xeon E5-2643 v3 系统上运行。

还有其他人遇到过这个问题吗?我希望转换的工作方式与 strtol 函数相同,并且不会报告任何流错误。我是否缺少一些流设置/忘记调用某些函数或在这里设置一些标志?

任何建议将不胜感激。

下面附上一个演示此问题的示例 C++ 程序。

#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstdint>


int main()
{
  const char* minus_one = "0xffffffff";

  std::stringstream ss;
  ss << minus_one;

  std::cout << "input string    : " << ss.str() << "\n"; // outputs "0xffffffff"

  // C-style conversion
  int32_t cint;
  cint = strtol(ss.str().c_str(), NULL, 0);
  std::cout << "strtol conv     : " << cint <<  " (" << std::hex << cint << ")\n"; // outputs "-1 (ffffffff)"
  std::cout << std::dec;

  // C++-style conversion
  int32_t cppint;
  ss >> std::hex >> cppint;
  std::cout << std::dec << "ssextr conv     : " << cppint <<  " (" << std::hex << cppint << ")\n"; // outputs "0 (0)" <- ERROR
  std::cout << std::dec;
  if (ss.fail()) std::cout << "Error converting number.\n";

  // C++-style conversion with cast
  uint32_t cppuint;
  int32_t cppint2;
  ss.clear();
  ss.str(minus_one);
  ss >> std::hex >> cppuint;
  cppint2 = (int32_t)cppuint;
  std::cout << std::dec << "ssextr cast conv: " << cppint2 <<  " (" << std::hex << cppint2 << ")\n"; // outputs "-1 (0xffffffff)"
  std::cout << std::dec;
  if (ss.fail()) std::cout << "Error converting number.\n";

  exit(EXIT_SUCCESS);
}

【问题讨论】:

  • 不存在负十六进制数。可以将十六进制值的位模式解释为有符号整数并获得负值,但这是另一回事。您尝试读取的数字 4294967295 不适合 32 位有符号整数,因此转换失败。如果您使用uint32_t,它可以正常工作,然后您可以随心所欲地解释这些位。
  • 我假设我已经明确表示我正在尝试将十六进制数解释为有符号的 32 位整数,我并不是字面上的意思是负十六进制数。这里提到的数字(4294967295 或十六进制的 0xffffffff)适合 32 位,因此它应该适合有符号和无符号整数 - 正如示例程序所演示的,当解释为有符号数时,即 -1。是的,我已经知道我可以将它读入 uint32_t,然后将其转换为 int32_t 并得到正确的结果,但我不应该这样做。
  • 您希望能够将4294967295 读入int32_t 吗?试试看,它也会失败,因为该值太大而无法在该类型中正确表示。
  • 您明确表示您希望它以某种方式运行,但实际上并非如此。如果您尝试将值读入语言知道不能表示该值的类型,那么就会出现问题。问题是您希望忽略该问题,而 c++ 不会忽略它。使用更大的 int,或者像你一样保持一个演员表。注意,您依赖于溢出有符号数字,这是未定义的行为。

标签: c++ stream hex negative-integer


【解决方案1】:

如果第一个十六进制位是f,则 c++ 将其设为“大数”:'0x7fffffff'。似乎c ++不想将其表示为负数。 像这样:

const char* minus_one = "0xf0000000";   //ssextr conv     : 2147483647 (7fffffff)
std::stringstream ss;
ss << minus_one;

// C++ style conversion
int32_t cppint;
ss >> std::hex >> cppint;
std::cout << std::dec << "ssextr conv     : " << cppint <<  " (" << std::hex << cppint << ")\n"; 
std::cout << std::dec;
if (ss.fail()) {
    std::cout << "Error converting number.\n";
}

【讨论】:

  • 0xf0000000 不是“减一”,而是 4026531840,太大了,无法在 int32_t 中表示。
【解决方案2】:
int32_t cint;
cint = strtol(ss.str().c_str(), NULL, 0);

这会将值0xffffffff 读入long,然后将其转换为int32_t。如果 long 大于 32 位,则 strtol 工作并返回 0xffffffff 即 4294967295,并将其转换为 int32_t 产生 -1。但这与从字符串中读取负数不同(如果 long 是 32 位,那么它不会按预期工作,而是返回 LONG_MAX 并将其转换为 int32_t,即 @ 987654332@).

int32_t cppint;
ss >> std::hex >> cppint;

这会尝试将值0xffffffff 读入int32_t,但值0xffffffff 不适合该类型,因此读取值失败(就像strtol 失败时long 是32 位)。

与您的strtol 版本更接近的是:

int32_t cppint;
long l;
if (ss >> std::hex >> l)
  cppint = l;
else
  // handle error ...

期望能够将值0xffffffff 读取为带符号的 32 位整数是不合理的。 strtol 和 istream 不读取位模式,它们读取数字,而数字 0xffffffff 不适合有符号的 32 位整数。

【讨论】:

  • 谢谢,这解释了我的误解。我相信十六进制数字实际上是一种位模式,我没有意识到它们被视为无符号。
  • 字符串 "0xffffffff" 不被视为无符号数,它被视为数字 4294967295,这是一个大的正值,不适合有符号的 32 位类型。字符串 "-0xffffffff" 将被视为负值(但也不适合有符号的 32 位值)。用十六进制写-1 的方式是-0x1 而不是0xffffffff。
  • 谢谢,我想我现在明白了。
【解决方案3】:

问题在于 unsigned 整数记录了十六进制表示法。 strtol 是一个 C 函数,显然更能容忍负整数的十六进制表示,并且在内部将字符串读取为无符号值,然后将其重新解释为有符号值。但即使在 C 语言中,对于 strtol 和无法以有符号类型表示的无符号值的转换,也未指定此类处理 结果是实现定义的或引发了实现定义的信号。 (来自 C11 6.3.1.3 [Conversions] 有符号和无符号整数的 1570 草案)

为了不破坏大量遗留代码,它可能以这种方式工作,但 C++ 是一种较新的语言,实施者已决定对十六进制表示更加严格。

【讨论】:

  • 谢谢,我没有意识到十六进制数字实际上被视为无符号数。我想我认为它们只是作为位表示。
  • 这个答案具有误导性,使用 istream 的 C++ 风格的代码并没有更严格,它只是在做一些不同的事情。请参阅我的答案,但简单地说,strtol 代码将值读入long,如果long 是64 位,则该值有效,然后将其转换为int32_t。这与像 istream 版本那样读入int32_t 不同。读入可以表示值的类型并转换与读入不能表示值的类型是不一样的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 1970-01-01
  • 1970-01-01
  • 2011-12-13
  • 1970-01-01
相关资源
最近更新 更多