有符号和无符号字符之间的比较
Posted
技术标签:
【中文标题】有符号和无符号字符之间的比较【英文标题】:comparison between signed and unsigned char 【发布时间】:2021-10-31 21:39:38 【问题描述】:我几乎认为这是一个愚蠢的问题......但我真的找不到答案。所以我在这里问这个。
为了了解隐式类型转换,我在 C 上运行以下代码。
#include <stdio.h>
int main()
unsigned char i;
char cnt = -1;
int a[255];
for (int k = 0; k < 255; k++)
a[k] = k;
for (i = cnt - 2; i < cnt; i--)
a[i] += a[i + 1];
printf("%d\n", a[i]);
return 0;
当我运行这个程序时,什么也没发生。
我在第一次迭代时发现for循环的循环条件为假,所以程序立即退出了for循环。
但是,我不明白为什么。
据我所知,C 在分配或比较不同类型的变量时会进行隐式转换。所以我认为在i = cnt - 2
上,减法运算使值-3,然后隐式转换为i 分配值253。
那么,条件 i < cnt
不应该为真,因为(由于有符号和无符号字符的比较,通过 cnt 的另一个隐式转换)253 小于 255?
如果不是,为什么这是错误的?有什么我遗漏的,或者有什么我不知道的例外吗?
【问题讨论】:
253 不小于 -3 :) cnt 数据类型不是 unsigned char,只是 char -128 到 127。如果你让 unsigned char cnt = -1。它会正常工作。 @Tevfik Kadan 我有点困惑。当我按照你说的做同样的事情时它确实有效,但是在做i < cnt
时不应该将cnt
自动转换为unsigned char
并变成255
吗?我刚试过printf("%d\n", (unsigned char)253 < (char) -1)
,它也给出了错误...
普通字符是否签名由实现定义。 学习隐式类型转换的目的 没有隐式类型转换这回事。根据定义,所有强制转换都是显式的。如果您想了解隐式类型转换,首先要学习的是不惜一切代价避免它们。
请参阅this discussion on implicit type promotions。 (如前所述,implicit 强制转换不是一回事。强制转换总是显式的。)
这是一个演员表:(type)。它正在使用强制转换运算符。这是 C 中唯一存在的强制转换,并且程序员始终是显式的,从而触发显式转换。还有各种隐式转换。但是没有隐式转换。
【参考方案1】:
你的问题一点也不傻。您已经接近解决方案:i
被赋值为 -3
,但隐式转换为 i
、unsigned char
的类型会将值更改为 253
。
为了更准确的解释,您的测试代码中有多个问题:
char
可能是有符号或无符号的,具体取决于平台和编译器配置,因此char cnt = -1;
可以将值-1
或255
存储到cnt
中,或者如果char
是无符号的,甚至可以存储其他值并且有超过 8 位。
for (i = cnt - 2; i < cnt; i--)
的行为还取决于默认情况下char
是有符号还是无符号:
在所有情况下,都会对测试 i < cnt
进行评估,同时将两个操作数都转换为 int
(或 unsigned int
,在极少数情况下是 sizeof(int)==1
)。如果int
可以表示char
和unsigned char
类型的所有值,则此转换不会更改值。
如果char
是无符号的并且有8 位,cnt
的值是255
,所以i
用值253
初始化,循环运行254 次,i
从253
向下到0
,然后i--
将值255
再次存储到i
,测试i < cnt
评估为假。循环打印507
,然后是759
,...32385
。
如果char
是有符号的并且有8 位,就像您的系统上可能的情况一样,cnt
的值是-1
并且i
被初始化为值-3
转换为unsigned char
,这是253
。初始测试i < cnt
评估为253 < -1
,这是错误的,导致立即跳过循环体。
您可以通过为编译器提供适当的标志(例如:gcc -funsigned-char
)来强制 char
默认为无符号并测试行为如何变化。使用 Godbolt's compiler explorer,您可以看到 gcc
仅生成 2 条指令,在有符号(默认)情况下返回 0,在无符号情况下返回预期输出。
【讨论】:
你在这里做得很好,指出了 C 语言中char
默认类型的歧义性令人不安的事情之一。我发现 this conversation 在这个主题上有一种平静的影响。
【参考方案2】:
首先让我们假设类型 char
的行为与类型相同
signed char
.
在这种情况下
i < cnt
由于整数提升,两个操作数都被隐式转换为 int
类型。
来自 C 标准(6.5.8 关系运算符)
3 如果两个操作数都有算术类型,通常的算术 执行转换。
和(6.3.1.8 常用算术转换)
1 许多期望算术类型的操作数的运算符导致 转换和产生结果类型以类似的方式。目的是为了 确定操作数和结果的通用实数类型。为了 指定操作数,每个操作数都被转换,不改变类型 域,对应的实数类型是共同实数的类型 类型。除非另有明确说明,普通实数类型也是 结果对应的真实类型,其类型域是 操作数的类型域(如果它们相同且复杂) 否则。这种模式称为通常的算术转换:
... 否则,对两个操作数都执行整数提升。然后 以下规则适用于提升的操作数:
如果两个操作数的类型相同,则不再进行转换 需要
和(6.3.1.1 布尔值、字符和整数)
...如果一个 int 可以表示原始类型的所有值(受限制 通过宽度,对于一个位域),该值被转换为一个 int; 否则,它将转换为无符号整数。 这些被称为 整数促销。
因此i
的正值表示为0000 0000 1111 1101
在整数提升之后将大于负值1111 1111 1111 1111
。
因此,for 循环的条件立即评估为逻辑假,因为 int
类型的正值 253
大于 int
类型的负值 -1
。
这是一个演示程序。
#include <stdio.h>
int main(void)
char cnt = -1;
unsigned char i = cnt - 2;
printf( "cnt = %x\n", ( unsigned int )cnt );
printf( "i = %x\n", ( unsigned int )i );
printf ( "i < cnt is %s\n", i < cnt ? "true" : "false" );
return 0;
程序输出是
cnt = ffffffff
i = fd
i < cnt is false
【讨论】:
以上是关于有符号和无符号字符之间的比较的主要内容,如果未能解决你的问题,请参考以下文章