我了解到在 C 语言中 char 类型的范围是从 -128 到 127,但它似乎不是那样

I learned that in C language char type ranges from -128 to 127, but it doesn't seem like that

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

#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

我给signed(normal) char类型赋值128,但是没有溢出

另外,c1和c2好像都是4bytes,-128和128是同一个值

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

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

在您的例子中,类型 char 的行为与类型 signed char 相同。在这种情况下 CHAR_MIN 等于 -128 并且 CHAR_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 具有相同的负值表示形式。

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

此处解释:https://en.wikipedia.org/wiki/Signed_number_representations

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

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

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

c1 = 128; 中,128 不适合您的 C 实现使用的带符号八位 char。 128 根据 C 2018 6.5.16.1 2 转换为 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 实现中一样,整数提升转换a charint,根据 6.3.1.1 2:“如果 int 可以表示原始类型的所有值……,则该值将转换为 int…… ”

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

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

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

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

在声明中

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]。我说 "at least" 因为 CHAR_BIT 可能大于 8(有使用 9 位字节和 36 位字的历史系统)。 signed char 将表示至少在 [-128..127] 范围内的值(同样,假设二进制补码)。

假设 char 是有符号的 8 位,然后将 128 分配给 c1 会导致 有符号整数溢出 并且其行为是 undefined,意味着编译器和执行环境不需要以任何特定方式处理它。 Any 结果是 "correct" 只要语言定义是必需的,无论它是否是您期望的结果。


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