为什么char类型不兼容signed char和unsigned char?

9
我发现C99标准中有一项声明,否认了类型char和类型signed char / unsigned char之间的兼容性。
C99标准的注释35:
CHAR_MIN在limits.h中定义,将具有值0或SCHAR_MIN之一,这可用于区分两个选项。无论做出什么选择,char都是与其他两种类型不兼容的单独类型。
我的问题是,为什么委员会否认了它们之间的兼容性?这是什么原理?如果char与signed char或unsigned char兼容,会发生什么可怕的事情吗?

请参见https://dev59.com/2HNA5IYBdhLWcg3wjOre?rq=1。 - Vaughn Cato
2
问题在于,如果在您的计算机上将“char”与其中一个兼容,那么它将在其他一些系统上与另一个兼容。这完全取决于底层硬件。委员会决定将其作为妥协而成为三种不同类型。 - Bo Persson
@Vaughn Cato 它们是相关的但不相同。我不理解“兼容性”的意思。 - junwanghe
对于 char 是有符号的实现,charsigned char 是不同的类型,但是你可以自由地在它们之间赋值(存在隐式转换)。指针类型没有隐式转换(除了涉及 void* 和空指针常量的特殊情况),因此 char*signed char* 不能互相赋值而不使用强制转换。使它们不兼容可以让编译器诊断错误,如果您将代码移植到普通 char 为无符号的系统上,则可能会出现真正的问题。 - Keith Thompson
就像Visual Studio 2019一样,微软的C编译器存在一个已知的bug,它将charsigned char视为相同类型。(它的C++编译器没有这个bug。)https://developercommunity2.visualstudio.com/t/_Generic-char-signed-char-unsigned-cha/1228885?preview=true 微软的回应是:“我们的团队优先处理对广大客户有影响的产品问题”。 - Keith Thompson
显示剩余4条评论
2个回答

11

这个问题的根源在于编译器历史。在80年代,基本上有两种C方言:

  1. 普通 char 是有符号的
  2. 普通 char 是无符号的

C89 应该标准化哪一个?C89 选择不标准化任何一个,因为它会使已经编写好的 C 代码的很多假设失效- 标准人士所说的“已安装基础”。所以 C89 做了和 K&R 一样的事情:保留了普通 char 的符号性是实现定义的。如果您需要特定的符号扩展,请使用限定符。

现代编译器通常可以用选项选择方言(例如 gcc 的 -funsigned-char)。

如果您忽略(char) 和 plain char之间的区别,可能会发生“可怕”的事情,即进行算术和移位操作而没有考虑到这些细节,可能会得到您不希望的符号扩展或相反的结果(甚至在移位时可能出现未定义的行为)。

还有一些愚蠢的建议,认为应始终使用显式的 signed 或 unsigned 限定符声明您的 chars。只要您仅使用指向这些限定类型的指针,这将起作用,但一旦涉及字符串和字符串函数(所有这些都是在指向普通 char 的指针上操作的),就“需要”丑陋的强制转换。这样的代码会突然出现大量难看到骨子里的转换。

char 的基本规则:

  • 对于字符串和需要传递普通 char 指针的函数,请使用 plain char
  • 如果需要对字节执行位操作和移位,请使用 unsigned char
  • 如果需要小的有符号值,请使用 signed char , 但是如果空间不是问题,可以考虑使用 int

2
我知道为什么有三种字符类型及它们的不同职责。但在某些实现中,char 被定义为具有与 signed char 或 unsigned char 相同的范围、表示和行为。现在范围、表示和行为都相同了,为什么 char 不兼容 signed char/unsigned char?兼容性的含义是什么? - junwanghe
发生在我身上(并带领我来到这里)的“可怕之事”,就是当使用char参数时,我的代码既没有使用有符号字符的模板代码,也没有使用无符号字符的模板代码,而我对此毫无头绪... - Eike
有许多简单的方法可以改进标准。一个明显的方法是要求实现选择并记录 char 的四种处理方式之一:作为 signed char 的别名、作为 unsigned char 的别名、作为与 signed char 不兼容的有符号类型,或作为与 unsigned char 不兼容的无符号类型。在许多系统上,前两个选择比后两个更容易实现,我看不出禁止它们有任何有用的目的。 - supercat

2
signed charunsigned char看作最小的算术整数类型,就像signed short/unsigned short一样,以此类推到intlong intlong long int等类型。这些类型都有明确的规定。
另一方面,char具有非常不同的目的:它是I/O和与系统通信的基本类型。它不是用于计算,而是作为数据的单位。这就是为什么你会在命令行参数中找到char,在“字符串”定义中找到char,在FILE*函数和其他读/写类型IO函数中找到它,以及在严格别名规则的例外中找到它。这种char类型故意定义得不那么严格,以便允许每个实现使用最“自然”的表示方式。
这只是责任分离的问题。
(虽然真的charsigned charunsigned char都具有布局兼容性,因此您可以明确地将一个转换为另一个。)

你可以将 char 看作是没有与之关联的整数类型的 byte - Sergey L.
@SergeyL.:根据定义,char 是最小可寻址单元,因此它确实是您所说的“字节”。只需记住它的位数不固定(至少为8位)。 - Kerrek SB

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