主要是历史原因。
char 类型的表达式在大多数情况下会提升为int(因为很多 CPU 没有 8 位算术运算)。在某些系统上,符号扩展是执行此操作的最有效方式,它主张使普通的 char 签名。
另一方面,EBCDIC 字符集具有带有高位集的基本字符(即,值为 128 或更大的字符);在 EBCDIC 平台上,char 几乎必须是未签名的。
ANSI C Rationale(适用于 1989 年标准)在这个问题上没有太多可说的;第 3.1.2.5 节说:
指定了三种类型的 char:signed、plain 和 unsigned。一种
普通的char 可以表示为有符号或无符号,具体取决于
在实施时,与以前的做法一样。类型signed char
被引入以提供单字节有符号整数类型
那些将普通字符实现为无符号的系统。出于以下原因
对称性,关键字signed 允许作为类型名称的一部分
其他整数类型。
更进一步,1975 年 C Reference Manual 的早期版本说:
char 对象可以在int 所在的任何地方使用。在所有情况下
char 被转换为 int 通过将其符号传播到上部
结果整数的 8 位。这与两人的一致
用于字符和整数的补码表示。
(但是,符号传播功能在其他
实现。)
这个描述比我们在后面的文档中看到的更具体,但它确实承认char 可能是签名的或未签名的。在“符号传播消失”的“其他实现”上,将char 对象提升为int 将对 8 位表示进行零扩展,本质上将其视为 8 位无符号量。 (该语言还没有 signed 或 unsigned 关键字。)
C 的直接前身是一种称为 B 的语言。B 是一种无类型语言,因此 char 是有符号还是无符号的问题不适用。有关 C 早期历史的更多信息,请参阅已故 Dennis Ritchie 的 home page,现为moved here。
至于您的代码中发生了什么(应用现代 C 规则):
char c = 0xff;
bool b = 0xff == c;
如果普通的char 是无符号的,那么c 的初始化会将其设置为(char)0xff,比较等于第二行中的0xff。但是如果对普通的char 进行签名,那么0xff(int 类型的表达式)将转换为char——但由于0xff 超过了CHAR_MAX(假设CHAR_BIT==8),结果是实现定义。在大多数实现中,结果是-1。在比较0xff == c时,两个操作数都转换为int,使其等价于0xff == -1,或者255 == -1,当然是假的。
另一个需要注意的重要事情是unsigned char、signed char 和(普通)char 是三种不同的类型。 char 与 either unsigned char 或 signed char 具有相同的表示;它是由实现定义的。 (另一方面,signed int 和 int 是同一类型的两个名称;unsigned int 是一个不同的类型。(除了只是为了增加轻浮性,它是实现定义的位字段是否声明为普通的int 有符号或无符号。))
是的,这有点乱,我敢肯定,如果今天从头开始设计 C,它的定义会有所不同。但是 C 语言的每个版本都必须避免破坏(太多)现有代码,以及在较小程度上破坏现有实现。