在C++中将十六进制字符串转换为无符号字符

5
我想把一个字符串中的十六进制表示转换成一个无符号字符变量,就像这样:
std::stringstream ss;
uint8_t x;
ss << "1f";
ss >> std::hex >> x;  // result: x = 0x31 (=49 in decimal and ='1' as char)

显然,我以为转换后x=0x1f(十进制为31),因为0x1f小于0xff,而0xff是用8位无符号字符可以存储的最大值。 但事实上只有我的字符串的前8位被用于转换。 有人能解释一下为什么会发生这种情况,以及如何解决吗?

你确定解析十六进制数不需要前缀0x吗?(例如,将流输入“0x1f”) - Shaggi
1个回答

8

std::uint8_t通常是unsigned char的别名(具体情况见下文),而相应的operator>>将其视为字符类型而不是整数类型。因此,字符'1'被读入x,其ASCII值为49。ASCII值的十六进制表示恰好是您想要解析的值的十进制表示,这只是巧合;尝试解析"1e""10""1xyz"仍将导致x == 49

为了解决这个问题,首先解析为另一种整数类型,然后缩小为8位:

std::stringstream ss;
uint8_t x;
unsigned tmp;

ss << "1f";
ss >> std::hex >> tmp; 
x = tmp;                // may need static_cast<uint8_t>(tmp) to suppress
                        // compiler warnings.

严谨的补充说明(主要是历史性的)

如果我们非常严谨,uint8_t是一种可选的实现定义的无符号整数类型,如果存在,则恰好为8位宽。C++将其定义推迟到C标准中的[cstdint.syn]/2,而C99在7.18.1.1中定义如下:

1 typedef名称intN_t指定具有宽度N、无填充位和二进制补码表示的带符号整数类型。因此,int8_t表示具有恰好8位宽度的带符号整数类型。

2 typedef名称uintN_t指定具有宽度N的无符号整数类型。因此,uint24_t表示具有恰好24位宽度的无符号整数类型。

3 这些类型是可选的。但是,如果实现提供了宽度为8、16、32或64位的整数类型,则必须定义相应的typedef名称。

这背后的原因是历史。曾经存在一些平台上,一个字节不一定有8位,例如一些PDPs(更不用说早期UNIVAC等十进制计算机)。今天这些已经很少有人关注,但它们在C被设计时很重要,因此在C标准中没有做出当今可能会做出的某些假设。

在这些平台上,8位整数类型并不总是容易提供,而且由于unsigned char被定义为恰好一个字节宽度,如果一个字节不是8位宽度,那么它不能同时恰好为8位宽度。这也是为什么所有的uintN_t类型都是可选的,并且为什么它们都没有与特定的整数类型绑定。意图是定义一些具有特定低级行为的类型。如果实现无法提供该行为,至少它会报错而不是编译出无意义的代码。

因此,非常严谨地说:如果您使用了uint8_t,则有可能编写出符合C++标准的实现,该实现会拒绝您的代码。也有可能编写出符合规范的实现,其中uint8_t是与unsigned char不同的整数类型,而问题中的代码仍然有效。

然而,在实践中,您不太可能遇到这样的实现。我所知道的所有当前的C++实现都将uint8_t定义为unsigned char的别名。3

1即使这还不是兔子洞的深度,虽然我怀疑C的创造者并没有考虑到Setun(一种俄罗斯平衡三进制计算机)。

2并非所有这些机器都将整数表示为二进制补码,例如。

3如果您知道有不符合此规则的机器,请留言告诉我,我会在这里记录下来。我想可能存在某些微控制器工具包有其自身的原因而偏离了这个规则。


只是好奇,为什么没有将std::uint8_t作为新的整数类型? - anxieux
@anxieux 首批引入 uint8_t 和其他固定宽度类型的 C 语言实现是基于现有整数类型的;在那里,uint8_t 是一个 unsigned char,这从未改变过。严格来说,可以编写一个执行您建议的操作的实现(请参见我编辑的严谨附录),但我认为从未这样做过。 - Wintermute

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接