【问题标题】:Why C and C++ hate signed char so much?为什么 C 和 C++ 如此讨厌带符号的字符?
【发布时间】:2014-02-06 05:03:12
【问题描述】:

为什么C允许使用“字符类型”访问对象:

6.5 表达式 (C)

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 一种字符类型。

但 C++ 只允许 charunsigned char

3.10 左值和右值 (C++)

如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义:

  • char 或 unsigned char 类型。

signed char hatred 的另一部分(引自 C++ 标准):

3.9 类型 (C++)

对于普通可复制类型 T 的任何对象(基类子对象除外),无论该对象是否拥有类型 T 的有效值,构成该对象的底层字节都可以复制到 char无符号字符。如果 charunsigned char 数组的内容被复制回对象,则该对象随后应保持其原始值。

并且来自 C 标准:

6.2.6 类型的表示 (C)

存储在任何其他对象类型的非位域对象中的值由 n × CHAR_BIT 位组成,其中 n 是该类型对象的大小,以字节为单位。该值可以被复制到 unsigned char [n] 类型的对象中(例如,通过 memcpy);生成的字节集称为值的对象表示。

我可以在 stackoverflow 上看到很多人说这是因为 unsigned char 是唯一保证没有填充位的字符类型,但 C99 部分 6.2.6.2 整数类型

signed char 不应有任何填充位

那么这背后的真正原因是什么?

【问题讨论】:

  • 您能否提供这些报价的位置
  • “仇恨”从何而来?我在这里没有看到任何问题。
  • 我认为如果从问题中删除无意义的 C 比较可能会更容易阅读。
  • @Ivan:不,我们不在乎。删除它。
  • @Close vote fetish club:这怎么“不清楚”?!

标签: c++ c char


【解决方案1】:

这是我对动机的看法:

在非二进制补码系统上,signed char 将不适合访问对象的表示。这是因为要么有两种可能的signed char 表示具有相同的值(+0 和 -0),要么有一种表示没有值(陷阱表示)。无论哪种情况,这都会阻止您做最有意义的事情,您可能会对对象的表示做一些事情。例如,如果您有一个 16 位无符号整数 0x80ff,则作为 signed char 的一个或另一个字节将陷入或比较等于 0。

请注意,在这样的实现(非二元补码)上,需要将普通的 char 定义为无符号类型,以便通过 char 访问对象的表示才能正常工作。虽然没有明确的要求,但我认为这是源自标准中其他要求的要求。

【讨论】:

  • 该标准的作者的理念似乎是,如果可能有一些实现在提供时会产生成本,那么没有理由强制所有实现做某事零收益。如果一项功能或保证在某些平台上会带来巨大的好处,而在其他平台上却没有,那么缺乏授权不应阻止实现在有意义的平台上支持它。实现应该只支持标准强制要求的有用特性和保证的想法似乎很大程度上是 21 世纪的发明。
  • @supercat 不得不责怪那些为多个平台编写代码的人,他们敢于希望含义保持一致。
  • @Caleth:如果标准想要帮助那些渴望一致含义的人,它应该提供一些以这种方式工作的类型。例如,我希望看到“uwrapN_t”类型,在支持它们的平台上,它们必须表现为不提升的无符号类型。当提供给运算符时,如果可能,它们应该产生自己的类型,如果没有,则强制编译错误。给定uwrap16_t a=0xC001;a*a 的值将是(uwrap16_t)0x8001,无论int 是 16 位、32 位还是其他。不幸的是,C 没有这样的类型。
【解决方案2】:

我认为您真正要问的是为什么 signed char 不符合所有规则,允许将类型双关语作为特殊情况char*。老实说,我不知道,尤其是因为——据我所知——signed char 也不能有填充:

[C++11: 3.9.1/1]: [..] 一个char、一个signed char和一个unsigned char占用相同的存储量并且具有相同的对齐要求(3.11);也就是说,它们具有相同的对象表示。对于字符类型,对象表示的所有位都参与值表示。 [..]

Empirical evidence suggests that it's not much more than convention:

  • char 被视为 ASCII 字节;
  • unsigned char 被视为具有任意“二进制”内容的字节;和
  • signed char 在风中飘扬。

对我来说,将其排除在这些标准规则之外似乎还不够,但老实说,我找不到任何相反的证据。我将把它归结为标准措辞中有点莫名其妙的奇怪之处。

(这可能是我们必须向std-discussion列表询问。)

【讨论】:

  • char 是正在使用的任何字符集的字节;据我所知,该标准并未表达对 ASCII 而不是 EBCDIC 的偏好。 (顺便说一句,在基于 EBCDIC 的系统上,char 必须是无符号的(假设它是 8 位)。)至于 signed char,它只是一个非常小的有符号整数类型,保证能够保持从 -127 到 + 的值127.跨度>
  • @PeterGibson:标准不保证补码。
  • @LightnessRacesinOrbit:如果结果可以在两种类型中表示,则可以很好地定义任一方向的转换。使用环绕(模 TYPE_MAX + 1)语义很好地定义了到无符号的转换。当值超出范围时,转换为有符号会产生实现定义的结果。
  • 在非二进制补码系统上,signed char 将不适合访问对象的表示。这样的实现肯定会将普通的char 定义为无符号类型。这可能是动机。
  • @MichaelBurr:这是因为有两种可能的signed char 表示具有相同的值,或者一种表示没有值(是陷阱表示)。无论哪种情况,这都会阻止您做最有意义的事情,您可能会对对象的表示做一些事情。例如,如果您有一个 16 位无符号整数 0x80ff,则一个或另一个字节,作为 signed char,将陷入或比较等于 0。
【解决方案3】:

使用字符类型来检查对象的表示是一种技巧。然而,它是历史性的,必须做出一些调整来允许它。

大多数情况下,在编程语言中,我们需要强类型。 float 的内容应作为 float 而不是 int 访问。这有很多好处,包括减少人为错误和实现各种优化。

但是,有时需要访问或修改对象的字节。在 C 中,这是通过字符类型完成的。 C++ 延续了这一传统,但通过取消使用 signed char 来稍微改善这种情况。

理想情况下,最好创建一个新类型,比如byte,并只允许通过这种类型对对象表示进行字节访问,从而分离常规字符类型,仅用作普通整数/字符。也许有人认为使用charunsigned char 的现有代码太多,无法支持这样的更改。但是,我从未见过 signed char 用于访问对象的表示,因此排除它是安全的。

【讨论】:

  • 这确实很有希望,但你能解释一下为什么 signed char 不一样吗?
  • 请检查 C++ 的 3.9 类型 部分和 C 的 6.2.6 类型表示 部分。这两个标准在此处仅提及 unsigned char,因此 C 不包括也有签名的字符。为什么他们都排除普通字符
  • @Ivan:C 在排除signed char 时并不纯粹。 6.5 7 允许访问具有“字符类型”的对象的表示。 6.5 6 允许将对象复制为“字符类型数组”。
  • @LightnessRacesinOrbit:目前还没有足够的确定性和参考资料将其放入答案中,但char 是默认值,因此被广泛使用,而使用unsigned char 是因为char在签名的实现中很讨厌,因此会干扰移位等,但没有理由选择使用signed char 来摆弄对象表示的字节,所以没有多少代码可以这样做。
猜你喜欢
  • 2015-10-07
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 2013-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多