在C ++中,sizeof('a') == sizeof(char) == 1
。这是直观的意义,因为'a'
它是字符文字,并且sizeof(char) == 1
由标准定义。
但是在C中,sizeof('a') == sizeof(int)
。即,看来C字符文字实际上是整数。有人知道为什么吗?我可以找到很多关于此C怪癖的提及,但没有解释为什么它存在。
在C ++中,sizeof('a') == sizeof(char) == 1
。这是直观的意义,因为'a'
它是字符文字,并且sizeof(char) == 1
由标准定义。
但是在C中,sizeof('a') == sizeof(int)
。即,看来C字符文字实际上是整数。有人知道为什么吗?我可以找到很多关于此C怪癖的提及,但没有解释为什么它存在。
Answers:
关于同一主题的讨论
“更具体地讲,整数提升。在K&R C中,如果不先将字符值提升为int,就几乎不可能使用(?)字符值,因此首先使字符恒定为int就消除了这一步骤。存在并且仍然是多字符常数,例如'abcd'或任何其他常数都可以放入int中。”
char
变量不是int,所以将字符常量设为1是一种特殊情况。而且很容易使用字符值而不提升它:c1 = c2;
。OTOH,c1 = 'x'
是向下转换。最重要的sizeof(char) != sizeof('x')
是,这是严重的语言缺陷。至于多字节字符常量:这是原因,但已过时。
最初的问题是“为什么?”
原因是文字字符的定义已经发展和改变,同时试图保持与现有代码的向后兼容性。
在C早期的黑暗日子里,根本没有类型。当我第一次学习用C语言编程时,已经引入了类型,但是函数没有原型来告诉调用者参数类型是什么。取而代之的是标准化了所有作为参数传递的内容,要么是一个int的大小(包括所有指针),要么是一个双精度数。
这意味着,当您编写函数时,所有不是双精度的参数都以ints形式存储在堆栈中,无论您如何声明它们,编译器都会在函数中放入代码来为您处理。
这使得事情有些前后矛盾,因此,当K&R撰写他们的著名著作时,他们提出了一个规则,即始终将字符文字提升为任何表达式中的int形式,而不仅仅是函数参数。
当ANSI委员会首次对C进行标准化时,他们更改了此规则,以使字符文字只是一个int,因为这似乎是实现同一目标的一种更简单的方法。
在设计C ++时,所有功能都必须具有完整的原型(尽管C被普遍接受为良好实践,但C中仍然不需要此功能)。因此,决定可以将字符文字存储在char中。在C ++中,这样做的优点是带有char参数的函数和带有int参数的函数具有不同的签名。在C中不是这种优势。
这就是为什么它们不同的原因。演化...
void f(unsigned char)
Vs的void f(signed char)
。
f('a')
可能希望f(char)
为该调用选择重载分辨率,而不是f(int)
。正如您所说,int
和的相对大小char
无关紧要。
我不知道为什么C中的字符文字是int类型的具体原因。但是在C ++中,有充分的理由不这样做。考虑一下:
void print(int);
void print(char);
print('a');
您希望打印调用选择带有字符的第二个版本。将字符文字设置为int将使这不可能。请注意,在C ++中,具有多个字符的文字尽管其值是由实现定义的,但仍然具有int类型。因此,'ab'
有类型int
,而'a'
有类型char
。
在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位,就像您怀疑的那样,但是字符文字是一个整数。
回到编写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可能需要读取与内存对齐的数据,这意味着所涉及的最低地址必须是数据大小的倍数:您可能能够读取地址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 )
我记得读过K&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;
}
我还没有看到它的基本原理(C char文字是int类型),但是Stroustrup不得不说些关于它的信息(来自Design and Evolution 11.2.1-Fine-Grain Resolution):
在C中,字符文字的类型
'a'
为int
。令人惊讶的是,在C ++中提供'a'
类型char
不会引起任何兼容性问题。除了病理示例外sizeof('a')
,可以用C和C ++表示的每个构造都给出相同的结果。
因此,在大多数情况下,它不会造成任何问题。
历史原因是C和它的前身B最初是在具有各种字长的DEC PDP微型计算机的各种模型上开发的,这些微型计算机支持8位ASCII,但只能对寄存器执行算术运算。(但是,不是PDP-11;稍后出现。)C的早期版本定义int
为机器的本机字大小,小于该值的任何值都int
需要扩展为int
与函数之间的传递。或用于按位,逻辑或算术表达式,因为这是基础硬件的工作方式。
这就是为什么整数提升规则仍说任何小于的数据类型int
都提升为的原因int
。由于类似的历史原因,C实现也可以使用补码数学而不是补码。与十六进制相比,八进制字符转义符和八进制常量是一等公民的原因同样是那些早期的DEC小型计算机的字长可分为三字节的块,但不能分为四字节的半字节。
char
正好是3个八进制数字长
这是正确的行为,称为“整体提升”。它也可能在其他情况下发生(如果我没记错的话,主要是二进制运算符)。
编辑:可以肯定的是,我检查了我的Expert C编程:Deep Secrets副本,并确认char文字不是以int类型开头。它是最初类型的炭,但是当它在使用表达式,它被提升到一个INT。这本书引用了以下内容:
字符文字的类型为int,它们遵循char类型的提升规则到达那里。第39页的K&R 1太简单地介绍了这一点,其中指出:
表达式中的每个char都将转换为int ....注意,表达式中的所有float都将转换为double...。由于函数参数是表达式,因此将参数传递给函数时也会进行类型转换:特别是,char和short变为int,float变为double。
我不知道,但是我猜想以这种方式实现它会更容易,而且并不重要。直到C ++,类型可以确定调用哪个函数时,才需要对其进行修复。
我确实不知道这一点。在原型存在之前,将任何比int窄的内容都用作函数参数时会转换为int。这可能是解释的一部分。
char
,以int
将使它很不必要的字符常量是整数。与此相关的是,该语言对待字符常量char
和变量的方式有所不同(通过给它们提供不同的类型),而需要的是对此差异的解释。
这仅与语言规范相切,但在硬件中,CPU通常只有一个寄存器大小-可以说是32位-因此,每当它在char上实际工作时(通过加,减或比较它)当将其装入寄存器时,将其隐式转换为int。编译器会在每次操作后适当地掩盖和移位数字,以便您在2(无符号字符)254上加上2,它将环绕到0而不是256,但是在硅芯片内部,它实际上是一个int直到将其保存回内存。
这有点学术性,因为该语言无论如何都可以指定8位文字类型,但是在这种情况下,语言规范恰好更紧密地反映了CPU的实际功能。
(x86眨眼可能会注意到,例如有一个本机addh op一步添加了短宽度寄存器,但是在RISC内核内部,这转化为两个步骤:添加数字,然后扩展符号,例如对上的add / extsh对。 PowerPC)
char
变量具有不同的类型。反映硬件的自动提升无关紧要-它们实际上是反相关的,因为char
变量是自动提升的,因此没有理由不使用字符文字char
。真正的原因是现在已经过时的多字节文字。