C语言中的简单字符解释



这是我的代码

 #include<stdio.h>
 void main()
 {
     char ch = 129;
     printf("%d", ch);
 }

我得到的输出为 -127。什么意思?

这意味着 char 是一个 8 位变量,只能容纳 2^8 = 256 个值,因为声明是 char chch 是一个signed变量,这意味着它可以存储 127 个负值和正值。 当您要求超过 127 时,该值从 -128 开始。

把它想象成一些街机游戏,你从屏幕的一侧走到另一侧:

ch = 50;

                                    ----->                        50 is stored
      |___________________________________|___________|           since it fits
    -128                       0         50          127          between -127
                                                                  and 128

ch = 129;

                                                    ---           129 goes over
      -->                                                         127 by 2, so
      |__|____________________________________________|           it 'lands' in
    -128  -127                 0                     127          -127

但是!! 你不应该依赖这个,因为它是未定义的行为!


为了纪念Luchian Grigore,以下是正在发生的事情的位表示:

char是一个变量,将容纳 8 位或字节。所以我们有 8 个 0 和 1 努力代表你想要的任何价值。如果charsigned变量,它将表示它是正数还是负数。您可能读过代表符号的位,这是对真实过程的抽象;事实上,它只是电子领域首批实施的解决方案之一。但是这样一个微不足道的方法有一个问题,您将有 2 种表示 0 (+0 和 -0( 的方法:

0 0000000     ->    +0        1 0000000     ->    -0                    
^                             ^ 
|_ sign bit 0: positive       |_ sign bit 1: negative

保证不一致!!因此,一些非常聪明的人想出了一个称为 Ones' Complement 的系统,它将表示一个负数作为其正对应物的否定(NOT 操作(:

01010101      ->    +85
10101010      ->    -85

这个系统...有同样的问题。0 可以表示为 00000000 (+0( 和 11111111 (-0(。然后出现了一些更聪明的人,他们创建了 Two's Complement,它将保留早期方法的否定部分,然后加上 1,因此删除了讨厌的 -0 并为我们的范围提供了一个闪亮的新数字:-128!。那么我们的范围现在是什么样子的呢?

00000000     +0
00000001     +1
00000010     +2
...
01111110     +126
01111111     +127
10000000     -128
10000001     -127
10000010     -126
...
11111110     -2
11111111     -1

因此,这应该可以了解当我们的小处理器尝试向变量中添加数字时会发生什么:

 0110010     50                   01111111     127
+0000010    + 2                  +00000010    +  2
 -------     --                   --------     ---
 0110100     52                   10000001    -127
     ^                                  ^       ^
     |_ 1 + 1 = 10          129 in bin _|       |_ wait, what?!

是的,如果你查看上面的范围表,你可以看到高达127(01111111(的二进制是很好和花花公子,没有什么奇怪的事情发生,但是在第8位设置为-128(10000000(之后,解释的数字不再保持其二进制大小,而是保持两者的补码表示。这意味着,二进制表示,变量中的位,1 和 0,我们心爱char的核心,确实持有 129...它在那里,看看它!但是邪恶的处理器读到,由于可怜的-127导致变量必须signed破坏其通过第一维欧几里得空间中的实数线进行臭味偏移的所有正潜力。

这意味着您遇到了未定义的行为

任何结果都是可能的。

char ch=129;是 UB,因为对于特定设置129不是char的可表示值。

您的char很可能是使用 Two 补码存储的 8 位有符号整数。 这样的变量只能表示 -128 到 127 之间的数字。 如果你做"127+1",它会换行到-128。 所以 129 相当于 -127。

这是因为char编码在一个字节上,因此有 8 位数据。

事实上,char有一个值编码在 7 位上,有一个位用于符号,unsigned char有 8 位数据作为其值。

这意味着:

将 abcdefgh 分别作为 8 位(a 是最左边的位,h 是最右边的位(,该值用 a 表示符号,bcdefgh 以二进制格式编码为实值:

42(十进制(= 101010(二进制(存储为 :阿布德夫格00101010

从内存中使用此值时:a 为 0:数字为正数,bcdefgh = 0101010:值为 42

当你把 129 时会发生什么:

129(十进制(= 10000001(二进制(存储为 :阿布德夫格10000001

从内存中使用此值时:a 为 0:数字为负数,我们应该减去 1 并将值中的所有位反转,因此 (bcdefgh - 1( 反转 = 1111111:值为 127数字是 -127<</p>

div class="ans>

在您的系统上:char 129 与 8 位有符号整数 -127 具有相同的位。无符号整数从 0 到 255,有符号整数从 -128 到 127。

相关(C++(:

您可能还有兴趣阅读什么是无符号字符的好答案?

正如@jmquigley指出的那样。这是严格未定义的行为,您不应依赖它。 允许 C/C++ class="ans>

char 类型是 8 位有符号整数。如果解释两者补码有符号表示中无符号字节 129 的表示,则得到 -127。

char的类型可以是signed也可以是unsigned,这取决于编译器。大多数编译器将其设置为"已签名"。

在您的情况下,编译器以静默方式将整数 129 转换为其有符号变体,并将其放入 8 位字段中,从而生成 -127。

char是8位,有符号。它只能保存值 -128 到 127。当您尝试为其分配129时,您会得到您看到的结果,因为指示签名的位被翻转了。另一种思考方式是数字"环绕"起来。

普通char是有符号的还是无符号的,都是实现定义的行为。在C语言中,这是一个相当愚蠢,晦涩的规则。 intlong等保证被签名,但char可以签名未签名,这取决于编译器的实现。

在您的特定编译器上,char显然已签名。这意味着,假设您的系统使用 2 的补码,它可以保存 -128 到 127 的值。

您尝试将值 129 存储在此类变量中。这会导致未定义的行为,因为您会收到整数溢出。严格来说,当你这样做时,任何事情都可能发生。该程序可以打印"hello world"或开始拍摄无辜的旁观者,并且仍然符合ISO C。在实践中,大多数(所有?(编译器会将这种未定义的行为实现为"环绕",如其他答案中所述。

总而言之,您的代码依赖于标准未明确定义的两种不同行为。了解这种不可预测的代码的结果如何以某种方式结束是有限的价值。这里重要的是认识到代码是晦涩难懂的,并学习如何以不晦涩的方式编写它。

例如,代码可以重写为:

unsigned char ch = 129;

甚至更好:

#include <stdint.h>
...
uint8_t ch = 129;
<小时 />

根据经验,请确保遵循MISRA-C:2004中的以下规则:

6.1 纯字符类型仅用于字符值的存储和使用。

6.2 有符号无符号字符类型仅用于存储和使用数值。

最新更新