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

时间:2009-01-11 22:44:00

标签: c++ c char sizeof

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

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

12 个答案:

答案 0 :(得分:36)

关于same subject

的讨论
  

“更具体地说是整体促销活动。在K& R C中它几乎是(?)   如果不首先将字符值提升为int,则无法使用字符值,   因此,首先使字符常量int消除了该步骤。   仍然存在多个字符常量,例如'abcd'或者   很多人都适合一个int。“

答案 1 :(得分:24)

最初的问题是"为什么?"

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

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

这意味着当你编写函数时,所有不加倍的参数都以int的形式存储在堆栈中,无论你如何声明它们,并且编译器将代码放入函数中来处理你。

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

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

当设计C ++时,所有函数都需要有完整的原型(C中仍然不需要这样做,尽管它被普遍认为是一种好的做法)。因此,决定将字符文字存储在char中。这在C ++中的优点是具有char参数的函数和具有int参数的函数具有不同的签名。这种优势在C中并非如此。

这就是他们与众不同的原因。进化...

答案 2 :(得分:21)

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

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

print('a');

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

答案 3 :(得分:18)

在我的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。

答案 4 :(得分:7)

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

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

这种东西在汇编语言中很常见 - 低8位将保存字符代码,其他位清零.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可能需要内存对齐读取,这意味着所涉及的最低地址必须是数据大小的倍数:您可以从地址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)

答案 5 :(得分:6)

我记得读过K&amp; R并看到一个代码片段,它会一次读取一个字符,直到它达到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;
}

答案 6 :(得分:5)

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

  

在C中,'a'等字符文字的类型为int。   令人惊讶的是,在C ++中提供'a'类型char不会导致任何兼容性问题。   除了病理学示例sizeof('a')之外,每个可以表达的构造   在C和C ++中都给出了相同的结果。

因此,在大多数情况下,它应该没有问题。

答案 7 :(得分:1)

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

编辑:只是为了确定,我查看了我的专家C编程:深层秘密的副本,我确认了一个字符文字不是开头的类型<强> INT 即可。它最初是 char 类型,但是当它在表达式中使用时,提升 int 。本书引用了以下内容:

  

字符文字的类型为int和   他们按照规则到达那里   从char类型的促销。这是   在页面上的K&amp; R 1中简要介绍了   39它说:

     

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

答案 8 :(得分:0)

我不知道,但我会猜测以这种方式实施它更容易,这并不重要。直到C ++,类型才能确定调用哪个函数需要修复它。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

这是一个学术观点,因为语言无论如何都可以指定一个8位的文字类型,但在这种情况下,语言规范恰好反映了CPU真正做的事情。

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

答案 11 :(得分:0)

造成这种情况的历史原因是C及其前身B最初是在各种字长的DEC PDP微型计算机的各种型号上开发的,它们支持8位ASCII,但只能对寄存器执行算术运算。 (但是,不是PDP-11;稍后出现。)早期的C版本将int定义为机器的本机字长,任何小于int的值都需要扩展为int是为了与函数传递或在按位,逻辑或算术表达式中使用,因为这是基础硬件的工作方式。

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