【问题标题】:I learned that in C language char type ranges from -128 to 127, but it doesn't seem like that我了解到在 C 语言中 char 类型的范围是 -128 到 127,但看起来不是这样
【发布时间】:2020-10-05 03:27:58
【问题描述】:

这可能是一个非常基本的问题,但我无法做到。 这是我正在使用的。

#include <stdio.h>

int main(void)
{
    char c1, c2;
    int s;
    c1 = 128;
    c2 = -128;

    s = sizeof(char);

    printf("size of char: %d\n", s);
    printf("c1: %x, c2: %x\n", c1, c2);
    printf("true or false: %d\n", c1 == c2);
}

结果是这样的。

size of char: 1
c1: ffffff80, c2: ffffff80
true or false: 1

我将值 128 分配给有符号(普通)字符类型,但它没有溢出。

此外,c1 和 c2 似乎都拥有 4 个字节,而 -128 和 128 是相同的值。

我怎样才能理解这些事实?我需要你的帮助。非常感谢。

【问题讨论】:

  • -128 的 4 字节整数的二进制补码是什么?
  • 我认为您看到的是promotion 和签名扩展。
  • 我对你的说法感到困惑。在这段代码中,您将 128 分配给了有符号字符类型,但它确实溢出了。这与您期望的范围是 -128 到 127 一致。
  • 2) %x 需要 unsigned int。您的char 正在升级中。
  • 您已经在标题中说范围是-128 to 127,那么您为什么要尝试使用(+)128

标签: c char signed integer-promotion internal-representation


【解决方案1】:

类型char 可以表现为signed char 类型或unsigned char 类型,具体取决于编译器选项或编译器的默认设置。

在您的情况下,类型 char 的行为与类型 signed char 相同。在这种情况下,CHAR_MIN 等于 -128CHAR_MAX 等于 127

所以char 类型的对象不能保存正数 128。在内部,此值具有以下十六进制表示 0x80。因此存储在char 类型的对象中,它被解释为负值,因为设置了符号位。这个负值是-128

所以在这些陈述之后

c1 = 128;
c2 = -128;

这两个对象的值相同,等于-128

还有输出

c1: ffffff80, c2: ffffff80

本次通话

printf("c1: %x, c2: %x\n", c1, c2);

表明提升为 int 类型的两个对象 c1c2 具有相同的负值表示。

请注意,为有符号类型的对象分配一个无法在对象中表示的正值是实现定义的行为。

【讨论】:

  • @HolyBlackCat 发出信号意味着行为未定义。例如,您可以获得一个陷阱值。
  • 非常感谢!你的解释很简单,完全有道理。我想我明白了。 Char 类型是 8 位,可以容纳 128,这就是为什么不会发生溢出错误,而 128 只是根据实现定义转换为 -128。
【解决方案2】:

这里解释:https://en.wikipedia.org/wiki/Signed_number_representations

如果 -128 和 128 以及介于两者之间的所有数字都用一个字节表示,那么该集合中将有 257 个数字。但是我们没有,它只有 256 个。

其映射如下十进制:[0..127,-128..-1] => [0b00000000..0b11111111]。请注意,第一位在 -128 处变为 1,很高兴;)。

您的字符串格式也不正确,您的编译器应该警告您,%x 需要 4 个字节!如果您考虑到我之前所说的,您会发现 0x80 确实是 0b10000000。

【讨论】:

    【解决方案3】:

    c1 = 128; 中,128 不适合您的 C 实现使用的有符号八位 char。根据 C 2018 6.5.16.1 2 将 128 转换为 char:“右操作数的值转换为赋值表达式的类型……”

    转换是实现定义的,根据 6.3.1.3 3:“否则,新类型是有符号的,值不能在其中表示;结果要么是实现定义的,要么是产生实现定义的信号。”您的 C 实现将 128(即 100000002 作为无符号二进制数字)转换为 -128,当对有符号二进制使用二进制补码时,它用相同的位表示。因此,结果是c1 包含值 -128。

    printf("c1: %x, c2: %x\n", c1, c2); 中,c1 被转换为int。这是因为使用 ... 参数调用函数的规则是将默认参数提升应用于相应的参数,根据 6.5.2.2 7:“默认参数提升是在尾随参数上执行的。”

    默认参数提升包括整数提升,根据 6.5.2.2 6. 当char 的范围比int 窄时,就像在大多数 C 实现中一样,整数提升将 char 转换为int,根据 6.3.1.1 2:“如果 int 可以表示原始类型的所有值……,则该值将转换为 int……”

    因此,在printf("c1: %x, c2: %x\n", c1, c2); 中,-128 的int 值作为第二个参数传递。您的 C 实现对int 使用 32 位二进制补码,其中 -128 用位 11111111111111111111111110000000 表示,我们可以将其用十六进制表示为 ffffff80。

    格式字符串使用%x 指定转换。 %x 的正确参数类型是 unsigned int。但是,您的 C 实现已接受 int 并将其位重新解释为 unsigned int。因此,11111111111111111111111110000000 位被转换为字符串“ffffff80”。

    这解释了为什么要打印“ffffff80”。这不是因为c1 有四个字节,而是因为它在传递给printf 之前被转换为四字节类型。此外,将负值转换为该四字节类型会导致设置了许多位的四个字节。

    关于c1 == c2 评估为真(1),这仅仅是因为c1 被赋予了值-128,如上所述,c2 = -128; 也将值-128 分配给c2,所以c1c2 具有相同的值。

    【讨论】:

    • 你的解释帮助我清除了脑海中的迷雾。现在我学习了整数转换、提升以及如何使用 %x 标识符。我需要学习阅读实现定义。语言特征。程序员每次需要确认语言功能时通常会查找(dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf)之类的网页吗?无论如何,非常感谢您的帮助。
    • @agongji 严格来说,该副本可能侵犯了版权。在en.cppreference.com/w/c/links 列出的草稿通常是公开的并且通常足够了。但是,是的,我想这里的许多海报(可能是那些以开发人员身份赚取收入的人)都拥有 ISO 标准的副本以及其他教科书。不过,大多数 C 语言技术问题都已在 SO 上得到正确回答。技术错误往往在这里得到快速可靠的纠正。但诚然,有时不清楚要搜索什么(“默认参数提升”!?)。
    • @agongji: 优秀的从业者对规则的学习足够好,以至于他们不需要经常查找规则,和/或养成避免查找规则的代码编写习惯,他们确实做到了必要时查看规则。一段时间后,您会熟悉各种文档,以便更快地找到其中的内容。
    【解决方案4】:

    在声明中

    printf("c1: %x, c2: %x\n", c1, c2);
    

    %x 需要unsigned int 类型的参数,因此c1c2 的值将从char 提升到unsigned int,并扩展了前导位。要将unsigned char 的数值打印为十六进制,您需要在转换中使用hh 长度修饰符:

    printf("c1: %hhx, c2: %hhx\n", c1, c2 );
    

    至于char 中可以表示的值,比这要复杂一些。

    基本字符集1成员的编码保证为非负数。附加字符的编码可能是负数或非负数。

    因此,取决于实现,一个普通的char 可以表示至少在[-128..127] 范围内的值(假设二进制补码表示) [0..255] .我说“至少”,因为CHAR_BIT 可能超过 8 个(历史上存在使用 9 位字节和 36 位字的系统)。 signed char 将表示至少在[-128..127] 范围内的值(同样,假设二进制补码)。

    假设char 是有符号的8 位,那么将128 分配给c1 会导致有符号整数溢出,其行为是未定义,这意味着编译器并且执行环境不需要以任何特定方式处理它。 任何结果都是“正确的”,只要语言定义是必需的,无论它是否是您预期的结果。


    1. 大写和小写拉丁字母、十进制数字、29 个图形字符、空格和控制字符(换行、换页、制表符等)。

    【讨论】:

    • 这里没有对 unsigned int 的提升。 c1c2 进行默认参数提升并最终(在这种情况下)成为 ints,并且由于它们没有正值,因此当与 stdarg 宏作为无符号整数一起使用时,行为是未定义的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2014-11-12
    • 2016-06-03
    • 1970-01-01
    • 2014-01-20
    相关资源
    最近更新 更多