为什么 C 字符的字面值是 int 而不是 char?

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

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

25648 次浏览

我记得阅读 K & R,看到一个代码片段,将读取一个字符的时间,直到它达到 EOF。由于所有字符都是文件/输入流中的有效字符,这意味着 EOF 不能是任何字符值。代码所做的就是将 read 字符放入 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;
}

我不知道,但我猜这样实现起来更容易,而且也没什么大不了的。直到 C + + 类型能够决定调用哪个函数时,它才需要被修复。

我真的不知道。 在原型存在之前,任何比 int 更窄的值在用作函数参数时都会被转换为 int。这可能是原因之一。

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

#include <stdio.h>
#define test(A) do{printf(#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;
};

当 run 给出:

'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。

关于 同样的话题的讨论

“更确切地说,是整体促销。在 K & R C,它实际上是(?) 如果字符值没有被提升为 int,就不可能使用它, so making character constant int in the first place eliminated that step. 过去和现在都有多个字符常量,比如“ abc d”或者其他 许多将适合于整型。”

我不知道 C 中的字符文字类型为 int 类型的具体原因。但是在 C + + 中,有很好的理由不这样做。想想这个:

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


print('a');

您可能期望打印调用选择采用 char 的第二个版本。如果字符串是整型的,那就不可能做到这一点。请注意,在 C + + 中,具有多个字符的文本仍然具有 int 类型,尽管它们的值是实现定义的。因此,'ab'的类型是 int,而 'a'的类型是 char

这就是正确的行为,叫做“积分升级”。它也可能发生在其他情况下(如果我没记错的话,主要是二进制运算符)。

编辑: 为了确保万无一失,我检查了我的 专家 C 编程: 深层秘密副本,并且我确认了字符文字不是 类型 Int。它最初的类型是 Char,但是当它在 表情中使用时,它是 升职了Int。书中引述如下:

字符文字的类型为 int 和 他们是按规矩办事的 从字符类型升级。这是 too briefly covered in K&R 1, on page 39页上写着:

表达式中的每个字符都是 转换成一个 int... 注意 all float's in an expression are 变成了双倍 函数参数是一个表达式, 类型转换也发生在 参数传递给函数 特别地,char 和 short 变成 int, 浮动变成双倍。

我还没有看到它的基本原理(C 字符文字是 int 类型) ,但是这里有一些 Stroustrup 不得不说的东西(来自 Design and Evolution 11.2.1-Fine-颗粒分辨率) :

在 C 中,字符文字(如 'a')的类型是 int。 令人惊讶的是,在 C + + 中使用 'a'类型 char并不会导致任何兼容性问题。 除了病理样本 sizeof('a')外,所有能被表达的构建体 在 C 和 C + + 中给出了相同的结果。

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

这与语言规范无关,但是在硬件中 CPU 通常只有一个寄存器大小——比如说32位——所以无论何时它在一个字符上实际工作(通过加、减或比较) ,当它被加载到寄存器中时,都会有一个隐式的 int 转换。编译器会在每次操作之后正确地屏蔽和移动数字,这样如果你把2加到(无符号字符)254,它会绕到0而不是256,但是在硅芯片内部,它实际上是一个整型数,直到你把它保存回内存。

这是一种学术观点,因为这种语言无论如何都可以指定一个8位的文字类型,但是在这种情况下,语言规范恰好更接近地反映了 CPU 实际上在做什么。

(x86学者可能会注意到,有一个本地的 addh 操作 例句,它在一个步骤中添加了短范围寄存器,但是在 RISC 核心内部,这转化为两个步骤: 添加数字,然后扩展符号,就像 PowerPC 上的 add/extsh 对一样)

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

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

This kind of thing's quite common in assembly language - the low 8 bits will hold the character code, other bits cleared to 0. PDP-11 even had:

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”到一个寄存器中,你会读取哪一个?

  • 有些 CPU 可能只支持将16位值直接读入16位寄存器,这意味着读取20或22位时需要清除“ X”中的位,根据 CPU 的字节顺序,需要将其中一个转换为低阶字节。

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

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

My guess is that C's simply carried this level of CPU-centric behaviour over, thinking of character constants occupying register sizes of memory, bearing out the common assessment of C as a "high level assembler".

(见 http://www.dmv.net/dec/pdf/macro.pdf第6-25页6.3.3)

最初的问题是“为什么”

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

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

这意味着在编写函数时,所有不是 double 的参数都以 int 形式存储在堆栈中,无论您如何声明它们,编译器都会在函数中放入代码来处理这个问题。

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

当 ANSI 委员会第一次将 C 标准化时,他们改变了这个规则,使字符文字只是一个 int,因为这似乎是实现同样目标的一种更简单的方法。

When C++ was being designed, all functions were required to have full prototypes (this is still not required in C, although it is universally accepted as good practice). Because of this, it was decided that a character literal could be stored in a char. The advantage of this in C++ is that a function with a char parameter and a function with an int parameter have different signatures. This advantage is not the case in C.

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

这样做的历史原因是,C 及其前身 B 最初是在各种字长不同的 DEC PDP 小型计算机上开发的,这些计算机支持8位 ASCII,但只能在寄存器上执行算术。(不过,PDP-11并非如此,它是后来才出现的。)早期版本的 C 将 int定义为机器的原生字大小,任何小于 int的值都需要扩大到 int,以便传递给函数或从函数传递,或用于位、逻辑或算术表达式,因为这是底层硬件的工作方式。

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