为啥 C 字符文字是整数而不是字符?

Posted

技术标签:

【中文标题】为啥 C 字符文字是整数而不是字符?【英文标题】:Why are C character literals ints instead of chars?为什么 C 字符文字是整数而不是字符? 【发布时间】:2010-09-30 20:07:52 【问题描述】:

在 C++ 中,sizeof('a') == sizeof(char) == 1。这很直观,因为'a' 是字符文字,而sizeof(char) == 1 是标准定义的。

然而,在 C 中,sizeof('a') == sizeof(int)。也就是说,看起来 C 字符文字实际上是整数。有谁知道为什么?我可以找到很多关于这个 C 怪癖的提及,但没有解释它为什么存在。

【问题讨论】:

sizeof 只会返回一个字节的大小,不是吗? char 和 int 的大小不相等吗? 这可能取决于编译器(和架构)。敢说你在用什么吗?标准(至少到 89 年)非常宽松。 没有。 char 总是 1 字节大,因此 sizeof('a') == 1 总是(在 c++ 中),而 int 理论上可以 sizeof 为 1,但这需要一个字节至少16 位,非常不太可能 :) 所以 sizeof('a') != sizeof(int) 在大多数实现中的 C++ 中非常可能 ...虽然在 C 中总是错误的。 'a' 是 C 中的 int - 句点。 C首先到达那里-C制定了规则。 C++ 改变了规则。您可以争辩说 C++ 规则更有意义,但更改 C 规则弊大于利,因此 C 标准委员会明智地没有触及这一点。 【参考方案1】:

我记得阅读 K&R 并看到一个代码 sn-p 一次读取一个字符,直到它到达 EOF。由于所有字符都是文件/输入流中的有效字符,这意味着 EOF 不能是任何 char 值。代码所做的是将读取的字符放入 int,然后测试 EOF,如果不是,则转换为 char。

我意识到这并不能完全回答您的问题,但如果 EOF 文字是,其余的字符文字将是 sizeof(int) 是有意义的。

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)

  *(p++) = (char) r;

【讨论】:

我不认为 0 是一个有效的字符。 @gbjbaanb:当然可以。这是空字符。想想看。您认为不应允许文件包含任何零字节吗? 阅读***-“EOF的实际值是一个系统相关的负数,通常为-1,保证不等于任何有效的字符代码。” 正如 Malx 所说 - EOF 不是 char 类型 - 它是 int 类型。 getchar() 和朋友返回一个 int,它可以保存任何 char 以及 EOF 而不会发生冲突。这实际上不需要文字字符具有 int 类型。 EOF == -1 在 C 的字符常量之后很久,所以这不是答案,甚至不相关。【参考方案2】:

我不知道,但我猜想以这种方式实现它更容易,而且这并不重要。直到 C++ 类型可以确定调用哪个函数时才需要修复它。

【讨论】:

【参考方案3】:

我确实不知道这一点。 在原型存在之前,任何比 int 更窄的东西在用作函数参数时都会被转换为 int。这可能是解释的一部分。

【讨论】:

另一个糟糕的“答案”。 charint 的自动转换将使字符常量成为整数非常不必要。相关的是,该语言对字符常量的处理方式与char 变量不同(通过赋予它们不同的类型),需要解释这种差异。 感谢您在下面给出的解释。您可能希望在答案中更全面地描述您的解释,它属于哪里,可以被投票,并且很容易被访问者看到。另外,我从来没有说过我在这里有一个好的答案。因此,你的价值判断没有任何帮助。【参考方案4】:

在我的 MacBook 上使用 gcc,我尝试:

#include <stdio.h>
#define test(A) doprintf(#A":\t%i\n",sizeof(A));while(0)
int main(void)
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
;

运行时给出:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

这表明一个字符是 8 位,就像你怀疑的那样,但字符文字是一个 int。

【讨论】:

+1 有趣。人们通常认为 sizeof("a") 和 sizeof("") 是 char* 的,应该给出 4(或 8)。但实际上它们在这一点上是 char[](sizeof(char[11]) 给出 11)。新手的陷阱。 字符文字没有提升为 int,它已经是 int。如果对象是 sizeof 运算符的操作数,则不会进行任何提升。如果有,这将破坏 sizeof 的目的。 @Chris Young:是的。查看。谢谢。【参考方案5】:

关于same subject的讨论

“更具体地说,是整体促销。在 K&R C 中,它实际上是(?) 如果不先将其提升为 int,则无法使用字符值, 因此,首先使字符常量 int 消除了该步骤。 过去和现在仍然存在多字符常量,例如 'abcd' 或 许多都适合 int。”

【讨论】:

多字符常量不可移植,即使在单台机器上的编译器之间也是如此(尽管 GCC 似乎跨平台是自洽的)。见:***.com/questions/328215 我会注意到 a) 此引文未注明出处;引文只是说“你不同意这个观点吗?这个观点是在过去讨论相关问题的帖子中发布的?” ... 和 b) 这是 可笑,因为 char 变量不是 int,所以将字符常量设为一个是一种特殊情况。并且很容易使用字符值而无需提升它:c1 = c2;。 OTOH,c1 = 'x' 是向下转换。最重要的是,sizeof(char) != sizeof('x'),这是严重的语言错误。至于多字节字符常量:它们是原因,但它们已经过时了。【参考方案6】:

我不知道 C 中的字符文字是 int 类型的具体原因。但在 C++ 中,有充分的理由不这样做。考虑一下:

void print(int);
void print(char);

print('a');

您会期望 print 调用选择采用 char 的第二个版本。将字符文字作为 int 会使这成为不可能。请注意,在 C++ 中,具有多个字符的文字仍然具有 int 类型,尽管它们的值是实现定义的。所以,'ab' 的类型为 int,而'a' 的类型为 char

【讨论】:

是的,“C++ 的设计和演变”说重载的输入/输出例程是 C++ 改变规则的主要原因。 Max,是的,我被骗了。我在兼容性部分查看了标准:)【参考方案7】:

这是正确的行为,称为“整体提升”。它也可能发生在其他情况下(如果我没记错的话,主要是二元运算符)。

编辑:为了确定起见,我检查了我的Expert C Programming: Deep Secrets 副本,并确认 char 文字不是类型int。它最初是 char 类型,但在 表达式 中使用时,它被提升int。以下内容来自本书:

字符文字的类型为 int 和 他们遵守规则到达那里 用于从 char 类型进行促销。这是 在 K&R 1 中过于简要地介绍,在第 39 上面写着:

表达式中的每个字符都是 转换为 int....注意 表达式中的所有浮点数都是 转换为双....自从 函数参数是一个表达式, 类型转换也发生在 参数被传递给函数:在 特别是 char 和 short 变成了 int, float 变为 double。

【讨论】:

如果要相信其他 cmets,则表达式 'a'以 int 类型开始 -- 在 sizeof() 内部不执行类型提升。 'a' 的类型为 int 似乎只是 C 的一个怪癖。 字符文字确实具有 int 类型。 ANSI/ISO 99 标准称它们为“整数字符常量”(以将它们与类型为 wchar_t 的“宽字符常量”区分开来)并明确指出,“整数字符常量的类型为 int。” 我的意思是它不是类型 int 开始,而是从 char 转换为 int(答案已编辑)。当然,这可能与编译器编写者以外的任何人无关,因为转换总是完成的。 不!如果您阅读 ANSI/ISO 99 C 标准,您会发现在 C 中,表达式“a”类型 int 开头。如果你有一个函数 void f(int) 和一个变量 char c,那么 f(c) 执行整数提升,但 f('a') 不会作为 'a' 的类型是已经 int。奇怪但真实。 "只是为了确定" -- 你可以通过实际阅读以下语句来更加确定:"字符文字具有 int 类型"。 “我只能假设这是一种无声的变化”——你错误地假设了。 C 中的字符文字一直是 int 类型。【参考方案8】:

我还没有看到它的基本原理(C char 文字是 int 类型),但是 Stroustrup 不得不说一下(来自 Design and Evolution 11.2.1 - Fine-Grain Resolution):

在 C 中,诸如'a' 之类的字符文字的类型是int。 令人惊讶的是,在 C++ 中给 'a' 类型 char 不会导致任何兼容性问题。 除了病态的例子sizeof('a'),所有可以表达的构造 在 C 和 C++ 中给出相同的结果。

所以在大多数情况下,它应该不会造成任何问题。

【讨论】:

有趣!与其他人关于 C 标准委员会如何“明智地”决定不从 C 中删除这个怪癖的说法有些矛盾。【参考方案9】:

这只是语言规范的切线,但在硬件中,CPU 通常只有一个寄存器大小——比方说 32 位——所以只要它实际在一个 char 上工作(通过加、减或比较它) 当它被加载到寄存器中时,它会隐式转换为 int。编译器会在每次操作后正确屏蔽和移动数字,这样如果您将 2 添加到 (unsigned char) 254,它将环绕到 0 而不是 256,但在硅内部它实际上是一个 int直到你把它存回内存。

这是一种学术观点,因为该语言本来可以指定一个 8 位文字类型,但在这种情况下,语言规范恰好更准确地反映了 CPU 实际在做什么。

(x86 专家可能会注意到例如一个本地 addh 操作,它可以一步添加短宽寄存器,但在 RISC 内核内部,这转换为两个步骤:添加数字,然后扩展符号,就像 PowerPC 上的 add/extsh 对)

【讨论】:

又一个错误的答案。这里的问题是为什么字符文字和char 变量具有不同的类型。反映硬件的自动提升不相关——它们实际上是反相关的,因为char 变量会自动提升,因此字符文字没有理由不属于char 类型。真正的原因是多字节文字,现在已经过时了。 @Jim Balter 多字节文字根本没有过时;有多字节 Unicode 和 UTF 字符。 @Crashworks 我们谈论的是多字节 character 文字,而不是多字节 string 文字。一定要注意。 Chrashworks 确实写了 characters。您应该写过 wide 字符文字(例如 L'à')确实占用更多字节,但不称为多字节字符文字。不那么自大会帮助你变得更准确。 @Blaisorblade 宽字符文字在这里不相关——它们与我写的内容无关。我是准确的,而您缺乏理解力,而您试图纠正我的虚假尝试是傲慢的。【参考方案10】:

在编写 C 时,PDP-11 的 MACRO-11 汇编语言有:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

这种事情在汇编语言中很常见 - 低 8 位将保存字符代码,其他位清除为 0。PDP-11 甚至有:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

这提供了一种将两个字符加载到 16 位寄存器的低字节和高字节中的便捷方法。然后你可以在别处写这些,更新一些文本数据或屏幕记忆。

因此,将字符提升为寄存器大小的想法是非常正常和可取的。但是,假设您需要将“A”放入寄存器,而不是作为硬编码操作码的一部分,而是从主内存中的某个位置包含:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

如果你只想从这个主存储器中将一个“A”读入寄存器,你会读哪个?

1234563 CPU 的一个或另一个需要转移到低位字节。

某些 CPU 可能需要内存对齐读取,这意味着所涉及的最低地址必须是数据大小的倍数:您可能能够从地址 24 和 25 读取,但不能从 27 和 28 读取。

因此,生成代码以将“A”放入寄存器的编译器可能更愿意浪费一点额外的内存并将值编码为 0 'A' 或 'A' 0 - 取决于字节顺序,并确保它是正确对齐(即不在奇数内存地址)。

我的猜测是,C 只是继承了这种以 CPU 为中心的行为,考虑到字符常量占用了内存的寄存器大小,从而证明了 C 作为“高级汇编程序”的普遍评价。

(请参阅http://www.dmv.net/dec/pdf/macro.pdf 第 6-25 页的 6.3.3)

【讨论】:

【参考方案11】:

最初的问题是“为什么?”

原因是文字字符的定义已经演变和改变,同时试图保持与现有代码的向后兼容。

在早期 C 的黑暗日子里,根本没有类型。当我第一次学习用 C 编程时,已经引入了类型,但是函数没有原型来告诉调用者参数类型是什么。相反,作为参数传递的所有内容都是标准化的,要么是 int 的大小(包括所有指针),要么是 double。

这意味着当您编写函数时,所有非双精度参数都以整数形式存储在堆栈中,无论您如何声明它们,编译器都会将代码放入函数中为您处理。

这让事情变得有些不一致,所以当 K&R 写他们的名著时,他们制定了这样的规则:在任何表达式中,字符文字总是会被提升为 int,而不仅仅是函数参数。

当 ANSI 委员会首次标准化 C 时,他们更改了此规则,以便字符文字只是一个 int,因为这似乎是实现相同目标的更简单方法。

在设计 C++ 时,要求所有函数都具有完整的原型(这在 C 中仍然不是必需的,尽管它被普遍接受为良好实践)。因此,决定字符文字可以存储在 char 中。在 C++ 中这样做的好处是带有 char 参数的函数和带有 int 参数的函数具有不同的签名。这个优势在 C 中是没有的。

这就是它们不同的原因。进化...

【讨论】:

+1 来自我的实际回答“为什么?”。但我不同意最后一个说法——“C++ 中 this 的优点是带有 char 参数的函数和带有 int 参数的函数具有不同的签名”——在 C++ 中,2 个函数仍然可以有参数相同的大小和不同的签名,例如void f(unsigned char)void f(signed char). @PeterK John 可以说得更好,但他所说的基本上是准确的。更改 C++ 的动机是,如果您编写 f('a'),您可能希望重载决议为该调用选择 f(char) 而不是 f(int)。正如您所说,intchar 的相对大小不相关。【参考方案12】:

其历史原因是C及其前身B最初是在各种型号的DEC PDP小型机上开发的,具有各种字长,支持8位ASCII但只能对寄存器进行算术运算。 (但不是 PDP-11;后来出现了。)C 的早期版本将 int 定义为机器的本机字长,并且任何小于 int 的值都需要扩大到 int 在order 传递给函数或从函数传递,或用于按位、逻辑或算术表达式,因为这就是底层硬件的工作方式。

这也是为什么整数提升规则仍然说任何小于int 的数据类型都被提升为int。出于类似的历史原因,C 实现也允许使用补码数学而不是二进制补码。与十六进制相比,八进制字符转义和八进制常量是一等公民的原因同样是,那些早期的 DEC 小型计算机的字长可分为三字节块,而不是四字节半字节。

【讨论】:

... 和 char 正好是 3 个八进制数字

以上是关于为啥 C 字符文字是整数而不是字符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥字符串文字是左值,而所有其他文字都是右值?

为啥我可以将字符分配给字符串对象而不是字符串对象的向量?

一个整数而不是字符串?为啥? [复制]

为啥 Python 返回整数而不是字符串 [关闭]

C ++打印从字符串字符派生的整数

为啥文字不是 const (字符串除外)?