char类型的符号问题

3
根据标准,char 是有符号还是无符号是实现定义的。这给我带来了一些麻烦。以下是一些例子:
1)测试最高位。如果 char 是有符号的,我可以简单地将值与 0 进行比较。如果是无符号的,我则需要将值与 128 进行比较。这两种简单方法都不是通用的,不能同时适用于两种情况。为了编写可移植的代码,似乎必须直接操作位,这不是很好。
2)值赋值。有时,我需要将一个位模式写入一个 char 值中。如果 char 是无符号的,可以使用十六进制表示法轻松完成,例如: char c = 0xff。但是这种方法在 char 是有符号的情况下不适用。以 char c = 0xff 为例。 0xff 超出了有符号 char 可以容纳的最大值。在这种情况下,标准规定 c 的结果值是实现定义的。
那么,有没有关于这两个问题的好主意?关于第二个问题,我想知道 char c = '\xff' 是否适用于有符号和无符号的 char
注意:有时需要向字符写入显式位模式。请参见http://en.cppreference.com/w/cpp/string/multibyte/mbsrtowcs中的示例。

3
如果您关心比特模式,也许您应该始终使用“unsigned char”。 - Brian Bi
有时候需要一个字符串,但是字符的值应该明确指定。请参考http://en.cppreference.com/w/cpp/string/multibyte/mbsrtowcs中的示例。 - Lingxi
测试 MSB:(x | 0x7F) != 0x7F - Tony Delroy
我真的看不出你测试 MSB 的理由。MSB 对于无符号和有符号来说是相同的位。 - Radiodef
我认为有足够的证据可以实际推断char c = '\xff'(以及类似的char str []= "\xff\xff")适用于有符号和无符号char。我发现的证据如下:1)http://en.cppreference.com/w/cpp/language/escape上的表格说明`'\xnn'`的表示是`byte nn。请注意使用了byte`这个词。2)en.cppreference.com/w/cpp/string/multibyte/mbsrtowcs中的示例使用了此方法。3)https://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Non-ASCII_Characters中的示例也是如此。 - Lingxi
显示剩余2条评论
5个回答

2

1) 测试MSB: (x | 0x7F) != 0x7F (或者 reinterpret_cast<unsigned char&>(x) & 0x80)

2) reinterpret_cast<unsigned char&>(x) = 0xFF;

请注意,如果您想将字符占用的内存视为一组位,而不是绕过与char类型中任何给定值相关联的特定位模式,则reinterpret_cast是完全适当的。


1)我认为(unsigned char)c >= 128更加简洁。 2)那么用一个字符串怎么样?我需要像en.cppreference.com/w/cpp/string/multibyte/mbsrtowcs示例中那样明确指定字符串中字符的位模式。 - Lingxi
对于第一点,if (c & 0x80) 看起来是最简洁的。无论 c 是作为有符号还是无符号提升,它都是正确的。 - Lingxi
@Lingxi:我使用0x7F的原因是因为它显然是正确的...如果你甚至需要问c & 0x80是否也正确,那么你不应该使用它,因为最好的情况下,每个查看你代码的程序员都可能会有同样的疑问。 - Tony Delroy
话虽如此,令人担忧的情况是 char 是有符号类型,相关段落来自 5/10 “如果两个操作数都是带符号整数类型或都是无符号整数类型,则具有较小整数转换级别的类型的操作数将被转换为具有较高级别的操作数的类型。”,所以字符 c 被提升为 signed int 类型的 0x80。如果系统使用二进制补码整数表示,则位于 0x80 的比特将保持设置状态。如果它使用符号/数量表示,则符号位将移动到最高有效位。因此,这是不可靠的。 - Tony Delroy

1
如果您真的关心数据的符号,只需根据需要将变量声明为signed charunsigned char。不需要使用与平台无关的位操作技巧。

0

实际上,你可以无需担心有符号性地做你想做的事情。

十六进制描述的是位模式而不是整数值。(见免责声明)

所以对于你说的第二点,你说你不能像这样赋值位模式:

char c = 0xff

但实际上你确实可以这样做,无论有无符号。

对于第一点,你可能无法使用“与0比较”的技巧,但仍然有几种方法可以检查最高位。一种方法是向右移动7位,在左侧填充零,并检查是否等于1。与有无符号无关。

正如Tony D指出的那样,(x | 0x7F) != 0x7F 是一个更具可移植性的方法,而不是进行移位,因为它可能不会填充零。类似地,你可以使用 x & 0x80 == 0x80。

当然,你也可以按照Brian的建议,直接使用无符号字符。

声明:Tony 指出 0x 实际上是 int 类型,当 char 无法容纳该值或者 char 是无符号的时候转换成 char 是实现定义的。然而,没有任何一个实现会在这里违反标准。无论是否有符号,char c = 0xFF 都会填充比特位,相信我。很难找到一种不这样做的实现。


你仍然可以向右移动7位,然后检查它是否等于1。标准将负值的右移定义为实现定义,因此不能保证可移植性(5.8/3“如果E1具有带符号类型和负值,则结果值是实现定义的。”)。 - Tony Delroy
0xffint 类型。请参考 http://en.cppreference.com/w/cpp/language/integer_literal。 - Lingxi
我认为从 int 赋值给 char 属于 4.7/3 中的 "如果目标类型是有符号的,则如果该值可以在目标类型(和位域宽度)中表示,则该值不变;否则,该值是 实现定义 的。" - Tony Delroy
如果 char 是有符号的,那么 char c = 0xFF 就是实现定义的。 - M.M

0

您可以使用两个值0x7F0xFF对给定的值进行OR和AND操作,以检测并去除其符号。


0

测试 MSB 最简单的方法是将其作为 LSB:char c = foo(); if ((c>>(CHAR_BIT-1)) & 1) ...

设置特定的位模式有点棘手。例如,全 1 位模式可能不一定是 0xff,而可能是 0x7ff,或者更现实的是 0xffff。无论如何,~char(0) 就是全 1 位模式。有点不太明显的是,char(-1) 也是全 1 位模式。如果 char 是有符号的,那很清楚;如果是无符号的,这仍然是正确的,因为无符号类型对 2^N 取模。按照这个逻辑,char(-128) 只设置了 8 位,而不管 char 中有多少位或它是否带符号。


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