为什么C和C ++非常讨厌签名char?

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

6.5表达式 (C)

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

  • 一个字符类型。

但C ++只允许charunsigned char

3.10左值和右值 (C ++)

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

  • char或unsigned char类型。

签名char仇恨的另一部分(引自C ++标准):

3.9类型 (C ++)

对于普通可复制类型T的任何对象(基类子对象除外),无论对象是否保持类型T的有效值,组成对象的基础字节都可以复制到charunsigned 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不应有任何填充位

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

这是我对动机的看法:

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

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

我认为你真正要问的是为什么signed char被取消了所有允许打字为char*作为特例的规则。 说实话,我不知道,特别是因为 – 据我所知 – signed char也不能填充:

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

经validation据表明它不仅仅是惯例 :

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

对我而言,似乎没有理由将其排除在这些标准规则之外,但老实说,我找不到任何相反的证据。 我将在标准措辞中将其归结为一种轻微莫名的怪异。

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

使用字符类型来检查对象的表示是一种破解。 但是,这是历史性的,必须做一些调整以允许它。

大多数情况下,在编程语言中,我们需要强类型。 作为float东西应该作为float而不是作为int 。 这有许多好处,包括减少人为错误和启用各种优化。

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

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