为什么char不同于 *both* signed char和unsigned char?

m3eecexj  于 2022-12-03  发布在  其他
关注(0)|答案(5)|浏览(177)

cppreference.com声明char
等效于signed charunsigned char [...],但char是一个不同的类型,与signed charunsigned char都不同
我假设这意味着char可以保存与unsigned charsigned char完全相同的值,但与任何一个都不兼容。为什么决定采用这种方式?为什么非限定char不像其他整数类型那样表示char具有适合平台的符号,其中int表示与signed int完全相同的类型?

jtw3ybtb

jtw3ybtb1#

三种C字符类型charsigned charunsigned char作为传统C实现和用法的编码而存在。
XJ 311委员会将C编入第一个C标准(现在称为C89),在Rational(斜体原文)中陈述了他们的目的:

1.1目的

该委员会的总体目标是为C编程语言制定一个清晰、一致和明确的标准,该标准编纂了C的通用、现有定义,并促进了用户程序在C语言环境中的可移植性。
X3 J11章程明确授权委员会 * 编纂现有的共同实践 ......
注意:X3 J11委员会特意强调他们正在编纂现有的C实现和常见用法/实践,以提高可移植性。
换句话说,“标准”C从来没有创建-现有的C代码、用法和实践都是编纂的。
根据3.1.2.5类型相同依据(粗体):
指定了三种类型的char:signed、普通字符和unsigned。普通字符可以表示为signedunsigned取决于实现,如以前的实践。引入类型signed char是为了在将普通字符实现为无符号字符的系统上提供单字节有符号整数类型。...
委员会的话很清楚:存在三种类型的char,因为普通char必须是signedunsigned,以便匹配“先前的实践”。因此,普通char必须是独立的-可移植代码不能依赖于普通char是带符号的还是不带符号的,但是signed charunsigned char必须都可用。
这三种字符类型不能以任何方式兼容,因为考虑到可移植性--而符合标准的C代码的可移植性是XJ 311委员会的主要目标之一。
如果extern char buffer[10]unsigned char buffer[10]在纯格式char为无符号的系统上兼容,则如果代码在纯格式char为有符号的 * 系统上编译,***in
unsigned char buffer[10]兼容,则代码的行为将有所不同。例如,X1 M20 N1 X的移位元素将根据X1 M21 N1 X是通过X1 M22 N1 X声明还是通过X1 M23 N1 X定义来访问而改变行为,从而破坏了可移植性。
在这种情况下,char可以用不同的行为进行签名或未签名,这一事实
已经存在**,委员会不可能在不违背“编纂C的通用现有定义”的目标的情况下改变这一点。
但是,出于提高可移植性的目的,没有任何理由创造一种疯狂的、导致可移植性噩梦的情况,即“有时char与这个兼容,而与那个不兼容,有时char与那个兼容,而与这个不兼容”。

    • 如果代码编译了-但这是一个假设,旨在演示 * 为什么 * 三个char类型必须不兼容。
6ovsh4lw

6ovsh4lw2#

TL;DR

向后兼容性。可能吧。也可能是他们不得不选择而不在乎。但我没有确定的答案。

长版本

简介
就像楼主一样,我更喜欢有可靠来源的答案,在没有可靠来源的情况下,有条件的猜测和推测总比没有好。
C语言中的很多东西都来自于向后兼容。当char是与signed char相同还是unsigned char由实现定义时,已经有很多C代码在那里,其中一些使用有符号字符,而另一些使用无符号字符。强制它是其中之一肯定会破坏一些代码。

为什么(可能)不重要

为什么非限定字符不表示平台适当的有符号字符
这并不重要。使用有符号字符的a实现保证CHAR_MIN等于SCHAR_MINCHAR_MAX等于SCHAR_MAX。无符号字符也是如此。因此,非限定的char总是与其限定的对应字符具有完全相同的范围。
根据标准5.2.4.2.1p2:
如果char类型的对象的值在表达式中使用时被视为有符号整数,则CHAR_MIN的值应与SCHAR_MIN的值相同,CHAR_MAX的值应与SCHAR_MAX的值相同。否则,CHAR_MIN的值应为0,CHAR_MAX的值应与UCHAR_MAX的值相同。
这给我们指出了一个方向,那就是他们根本不在乎,或者说“感觉更安全”。
C标准中另一个有趣的提及是:
所有枚举都有基础类型。基础类型可以使用枚举类型说明符显式指定,并且是其固定基础类型。如果未显式指定,则基础类型是枚举的兼容类型,该类型是有符号或无符号整数类型(不包括位精度整数类型)或char

中断此(推测)可能出现的问题

我试图想出一个这样的场景,它实际上会产生问题。一个可能导致问题的场景是,如果你用一个使用signed char的编译器将源文件编译成一个共享库,然后在另一个使用unsigned char的编译器编译的源文件中使用这个库。
即使这不会导致问题,想象一下共享库是用一个前ansi编译器编译的,我也不能肯定这会导致问题,但我可以想象它会。
而另一个猜测来自史蒂夫首脑会议在评论区:
我在推测,但是:如果该标准要求,用Eric的话来说,“char与实现定义的signed charunsigned char选项的类型相同,”那么,如果我在一个charsigned char相同的平台上,我可以毫无警告地混合这两个选项,并创建不可移植到char默认为无符号的机器的代码。因此,定义“charsigned charunsigned char的不同类型“有助于迫使人们编写可移植代码。

向后兼容性是一项神圣的功能

但是请记住,C标准的幕后人员过去和现在都非常关心不破坏向后兼容性。甚至到了他们不想更改某些库函数的签名以返回const值的地步,因为这会产生警告。不是错误。警告!可以很容易地禁用的警告。相反,他们只是在标准中写了修改值是未定义的行为。你可以在这里读到更多关于这方面的内容:https://thephd.dev/your-c-compiler-and-standard-library-will-not-help-you
因此,每当你在C语言中遇到非常奇怪的设计选择时,你可以很好地打赌,向后兼容性就是原因。这就是为什么你可以用0初始化指向NULL的指针,即使对于一个NULL不是零地址的机器也是如此。这也是为什么bool是一个关键字_Bool的宏。
这也是为什么按位|&==具有更高优先级的原因,因为有很多(安装在三(3)台机器上的几百KB:)源代码,包括像if (a==b & c==d)这样的东西。Dennis里奇承认他应该修改它。https://www.lysator.liu.se/c/dmr-on-or.html

因此,我们至少可以肯定地说,有些设计选择是考虑到向后兼容性的,后来那些做出选择的人承认这是错误的,而且我们有可靠的来源。

C++
同时记住你的源代码指向C源代码。在 C 中,有些原因不适用于C。比如重载。

quhf5bfb

quhf5bfb3#

对于普通的char,不强制要求带符号或不带符号的原因之一是IBM大型机上使用的EBCDIC代码集。
在§6.2.5 Types ¶3中,C标准说:
声明为char类型的对象足够大,可以存储基本执行字符集的任何成员。如果基本执行字符集的成员存储在char对象中,则保证其值为非负

  • 着重号为后加。*

现在,在EBCDIC中,小写字母的代码点为0x 81 - 0x 89、0x 91 - 0x 99、0xA 2 - 0xA 9;大写字母具有代码点0xC 1 - 0xC 9、0xD 1 - 0xD 9、0xE 2 - 0xE 9;数字的代码点为0xF 0 - 0xF 9。因此:

  • 字母不连续。
  • 小写字母在大写字母之前排序。
  • 数字的排序高于字母。
  • 根据6.2.5节的规定,char的类型必须是无符号的。

前三点中的每一点都与ASCII(以及ISO 8859和ISO 10646,即Unicode)形成对比。

carvr3hs

carvr3hs4#

原因是向后兼容性。这里有一些关于它背后的历史的研究。它只使用权威的第一来源,如丹尼斯M.里奇(C的创造者)或ISO的出版物。
最初,只有intchar,C的早期草案被称为“NB”,代表“新B”,包括了这些在前代B和BCPL中没有的新类型[里奇,93]:
......似乎有必要制定一个打字方案来科普字符和字节寻址,并为即将到来的浮点硬件做准备。

胚胎C

NB存在的时间很短,所以没有写完整的描述。它提供了intchar类型,它们的数组,以及指向它们的指针,声明的风格典型为

int i, j;
char c, d;

后来添加了unsigned [里奇,93]:
在1973-1980年期间,该语言有所发展:型结构得到unsignedlong...
请注意,这是指此时独立的“类型限定符”unsigned,相当于unsigned int
大约在1978年的这个时候,The C Programming Language 第1版出版了[Kernighan,78],在第2.7章中提到了与char相关的类型转换问题:
关于字符到整数的转换,有一个微妙的问题。该语言没有指定char类型的变量是有符号还是无符号量。当char被转换为int时,它能产生负整数吗?不幸的是,这会因机器而异,反映出体系结构的不同。在一些机器上例如,在PDP-11中,最左边位为1的char将被转换为负整数(“符号扩展”)。在其他情况下,char通过在左端添加零而被提升为int,因此总是正的。
在这一点上,到int的类型提升被描述为有问题的,而不是char的符号性,甚至没有指定。上述文本在第二版中基本保持不变[Kernighan,88]。
在第1版[Kernighan,78,2.2]中,unsigned只能应用于int,并被视为限定符:
此外,还有许多限定符可应用于intshortlongunsigned中的一个或多个。
鉴于第2版符合标准C [Kernighan,88,2.2]:
限定符signedunsigned可以应用于char或任何整数。/--/普通char是否带符号取决于机器,但可打印字符始终为正数。
因此在第1版和第2版之间,他们发现了一个向后兼容性问题,将新的unsigned/signed(现在称为 * 类型说明符 *,而不是限定符[ANSI/ISO,90])应用到char类型,与第1版中已经确定的类型转换相同。
这种兼容性问题在80年代后期的标准化过程中仍然存在。我们可以从各种理由中读到这一点,例如[ISO,98,6.1.2.5 §30]
指定了三种类型的charsigned、纯格式和unsigned。纯格式char可以表示为有符号或无符号,这取决于实现方式,如以前的实践中那样。引入类型signed char是为了在那些将纯格式char实现为无符号的系统上提供单字节有符号整数类型。出于对称的原因,允许关键字signed作为其他整型的类型名称的一部分。指定了两种整型:signedunsigned。如果两个说明符都没有使用,则假定为signed。在基文档中,唯一的unsigned类型是unsigned int
这实际上表明,signed int被允许使intchar更对称,而不是相反。
资料来源:

0lvr5msh

0lvr5msh5#

你引用的这句话实际上根本不是来自C标准,而是来自C标准。你链接到的网站(cppreference.com)主要是关于C的,那里的C内容是事后才想到的。
这一点对于C很重要(但对于C来说并不重要),因为C允许基于类型的重载,但你只能重载 distinct 类型。char必须与signed charunsigned char都不同,这意味着你可以安全地重载这三个类型:

// 3 overloads for fn
void fn(char);
void fn(signed char);
void fn(unsigned char);

并且不会出现关于不明确重载等错误。

相关问题