为什么C和C++如此讨厌签名字符

Why C and C++ hate signed char so much?

本文关键字:讨厌 字符 C++ 为什么      更新时间:2023-10-16

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

6.5 表达式(C)

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

  • 字符类型。

但是C++只允许字符无符号字符

3.10 左值和右值(C++)

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

  • 字符或无符号字符类型。

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

3.9 类型(C++)

对于简单可复制类型 T 的任何对象(基类子对象除外),无论该对象是否具有 T 类型的有效值,都可以将构成该对象的基础字节复制到char无符号字符数组中。如果将 char 数组或无符号 char的内容复制回对象,则对象随后应保持其原始值。

从 C 标准:

6.2.6 类型(C)的表示

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

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

签名字符不得有任何填充位

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

以下是我对动机的看法:

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

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

我认为你真正要问的是为什么signed char被取消了所有允许类型双关语作为特例char*的规则的资格。老实说,我不知道,特别是因为——据我所知——signed char也不能有填充:

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

经验证据表明,它只不过是惯例:

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

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

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

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

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

但是,有时需要访问或修改对象的字节。在 C 语言中,这是通过字符类型完成的。C++延续了这一传统,但通过消除为此目的使用signed char,情况略有改善。

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