为什么C字符文字整数而不是字符?

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

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

关于同一主题的讨论

“更具体地说是积分促销,在K&R C中,几乎不可能使用一个字符值,而不把它先提升到int,所以使得字符常量int在第一位消除了这个步骤。诸如“abcd”之类的常量或者其他许多将适合int。

我不知道为什么C中的字符文字是inttypes的具体原因。 但在C ++中,有一个很好的理由不要这样做。 考虑这个:

 void print(int); void print(char); print('a'); 

你会期望打印的电话select第二个版本采取一个字符。 有一个字符文字是一个int将使这是不可能的。 请注意,在C ++中,具有多个字符的文字仍然有inttypes,尽pipe它们的值是实现定义的。 所以, 'ab'inttypes,而'a'chartypes。

在我的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; }; 

运行时给出:

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

原来的问题是“为什么?”

原因在于字面字符的定义已经发展和改变,同时试图保持向后兼容现有的代码。

在C早期的黑暗时代,根本没有types。 当我第一次学习用C语言编程时,引入了types,但是函数没有原型来告诉调用者什么types的参数。 相反,它是标准化的,所有作为parameter passing的将是一个int的大小(这包括所有的指针),或者它将是一个双。

这就意味着当你写这个函数的时候,所有不是double的参数都会以int的forms存储在堆栈中,不pipe你怎么声明它们,编译器会把代码放在函数中来处理。

这使得事情有些不一致,所以当K&R编写他们的着名书籍时,他们把规则中的字符总是在任何expression式中提升为int,而不仅仅是函数参数。

当ANSI委员会首先对C进行标准化时,他们改变了这个规则,这样一个字符就会成为一个整数,因为这似乎是一个简单的方法来实现同样的事情。

当deviseC ++时,要求所有函数都有完整的原型(这在C中仍然不是必需的,虽然它被普遍接受为良好实践)。 正因为如此,决定可以将字符文字存储在字符中。 这在C ++中的优点是具有char参数的函数和具有int参数的函数具有不同的签名。 这个优点在C中并不是这样。

这就是为什么他们不同。 演化…

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

  • 某些CPU可能只能直接支持将一个16位值读入一个16位寄存器,这意味着在20或22的读操作将要求清除“X”位,并根据CPU的字节序将需要移入低位字节。

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

因此,编译器生成代码以获得“A”进入寄存器可能更喜欢浪费一点额外的内存,并根据字节顺序将值编码为0“A”或“A”0,并且还要确保其正确alignment(即不在奇数内存地址)。

我的猜测是,C只是把这种以CPU为中心的行为放在了一边,考虑到占用内存寄存器大小的字符常量,将C作为“高级汇编器”的通用评估。

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

我记得读过K&R,看到一个代码片段,它会一次读取一个字符,直到碰到EOF。 由于所有的字符都是有效的字符,所以在文件/inputstream中,这意味着EOF不能是任何char值。 代码所做的是将读取的字符放到一个int中,然后testingEOF,如果不是,则转换为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 char文字是inttypes),但是这是Stroustrup不得不说的一些东西(来自Design and Evolution 11.2.1 – Fine-Grain Resolution):

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

所以大部分应该不会造成任何问题。

这是正确的行为,称为“整体推广”。 也可能发生在其他情况下(如果我没有记错的话,主要是二元操作符)。

编辑:只是可以肯定的,我检查了我的专家C编程的副本:深秘密 ,我确认了字符文字不以typesint 开始 。 它最初是chartypes的,但在expression式中使用时,它被提升为 int 。 以下是从书中引用:

字符文字有inttypes,他们通过遵循从chartypes的升级规则到达那里。 这在第39页的K&R 1中简短地介绍过,它说:

expression式中的每个字符都被转换为一个int …注意,expression式中的所有float都被转换为double ….由于函数参数是一个expression式,当parameter passing给函数时,types转换也会发生:in特别是char和short变成int,float变成了double。

我不知道,但我会猜测这样实现它更容易,这并不重要。 直到C ++,types才能确定哪个函数被调用,需要修复。

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

这只是与语言规范相切,但在硬件中,CPU通常只有一个寄存器大小 – 32位,假设 – 所以只要它实际上在字符上工作(通过加,减,或比较)当它被加载到寄存器时隐式转换为int。 编译器负责在每次操作之后正确地屏蔽和移位数字,这样如果你添加2(unsigned char)254,它将会绕到0而不是256,但是在芯片内部它实际上是一个int直到您将其保存回内存。

这是一个学术问题,因为语言本来可以指定一个8位的文字types,但是在这种情况下,语言规范恰好反映了CPU的真正作用。

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