【问题标题】: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 标准委员会明智地没有触及这一点。

标签: c++ c char sizeof


【解决方案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) 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。

    【讨论】:

    • +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 似乎跨平台是自洽的)。见:stackoverflow.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 个八进制数字
      猜你喜欢
      • 1970-01-01
      • 2018-06-08
      • 2015-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-11
      • 2020-09-25
      相关资源
      最近更新 更多