10

代码:

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
using std::cerr;
using std::cout;
using std::stringstream;
using std::string;
using std::for_each;

void convert(const string& a_value)
{
    unsigned short i;
    if (stringstream(a_value) >> i)
        cout << a_value << " converted to " << i << ".\n";
    else
        cerr << a_value << " failed to convert.\n";
}

int main()
{
    string inputs[] = { "abc", "10", "999999999999999999999", "-10", "0" };
    for_each(inputs, inputs + (sizeof(inputs)/sizeof(inputs[0])), convert);
    return 0;
}

Visual Studio 编译器(v7、v8、v9、v10)的输出:

abc 转换失败。
10 转换为 10。
999999999999999999999 转换失败。
-10 转换为 65526。
0 转换为 0。

g++ 的输出(v4.1.2、v4.3.4):

abc 转换失败。
10 转换为 10。
999999999999999999999 转换失败。
-10 转换失败。
0 转换为 0。

我预计"-10"无法将其转换为,unsigned short但它在 VC 编译器中成功。这是一个:

  • VC 编译器中的错误?
  • GNU 编译器中的错误,我的期望不正确?
  • 实现定义的行为?
4

2 回答 2

5

答案取决于您使用的 C++ 版本。C++03 及更早版本要求输入符合 what sscanfdoes(这里使用"%hi"输入说明符),并将sscanf整数值读入(有符号)短整型,不检测溢出;然后将结果(通过隐式转换)分配给您的unsigned short. C++11 需要调用的等价物strtoull,它不允许 -符号,并且在溢出的情况下需要一个错误(这是未定义的行为sscanf,因此是 C++03)。

在实践中,C++03 的所有合理实现都检查过低,并且在这种情况下的“未定义行为”对应于现在需要的。另一方面,他们被要求接受现在(逻辑上)禁止的减号。

编辑(更正):在重新阅读要求时strtoull,我发现它确实需要接受减号。因此,尽管看起来很愚蠢,但该标准确实需要输入无符号整数类型才能接受减号。(还要注意, 的行为strtoull取决于全局 C 语言环境,这可能会接受额外的可能性。)

编辑(进一步澄清):正如 ectamur 指出的那样,这应该是一个错误(在 C++11 中),因为(unsigned long long)( -10 )太大而无法在unsigned short. 另一方面,在 C++03 之前它仍然是未定义的行为(这可能是 VC++ 所遵循的——所以无论他们做什么都是“正确的”)。

于 2012-10-24T09:13:27.493 回答
3

g++ 是正确的。无符号整数类型的算术提取器在 27.7.2.2.2p1 中定义为取决于num_get<>; 22.4.2.1.2p3 规定:

第 3 阶段:在第 2 阶段(字段)中累积的 s 序列char通过 [...] 的规则转换为数值 - 对于无符号整数值,函数strtoull.

并且存储的数字应该是

— 最正的可表示值,如果该字段表示的值太大而无法在 中表示valios_base::failbit分配给err

在 的操作上,C++ 遵循 C,这对于尝试将负号字段转换为;strtoull的结果有点不清楚。strtoull它声明“转换产生的值被否定(在返回类型中) ”,这unsigned long long将导致符号换行(到ULONGLONG_MAX - 10 + 1)。

因此strtoull返回一个太大而无法在 中表示的值unsigned short,并且num_get需要存储USHORT_MAX和设置失败位。

另一方面, 22.4.2.1.2p3 还指出存储的数字应该是(我的重点):

— 可表示的最大负值或无符号整数类型为零,如果该字段表示的值太大而不能用负表示valios_base::failbit分配给err

该子句的存在表明strtoull对于带负号的字段不严格遵循 的规则;在这种解释下num_get,需要存储0和设置失败位。

在任何一种情况下,转换都需要失败。

于 2012-10-24T09:12:54.537 回答