有符号和无符号字符之间的比较

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 &lt; cnt 不应该为真,因为(由于有符号和无符号字符的比较,通过 cnt 的另一个隐式转换)253 小于 255?

如果不是,为什么这是错误的?有什么我遗漏的,或者有什么我不知道的例外吗?

【问题讨论】:

253 不小于 -3 :) cnt 数据类型不是 unsigned char,只是 char -128 到 127。如果你让 unsigned char cnt = -1。它会正常工作。 @Tevfik Kadan 我有点困惑。当我按照你说的做同样的事情时它确实有效,但是在做i &lt; cnt 时不应该将cnt 自动转换为unsigned char 并变成255 吗?我刚试过printf("%d\n", (unsigned char)253 &lt; (char) -1),它也给出了错误... 普通字符是否签名由实现定义。 学习隐式类型转换的目的 没有隐式类型转换这回事。根据定义,所有强制转换都是显式的。如果您想了解隐式类型转换,首先要学习的是不惜一切代价避免它们 请参阅this discussion on implicit type promotions。 (如前所述,implicit 强制转换不是一回事。强制转换总是显式的。) 这是一个演员表:(type)。它正在使用强制转换运算符。这是 C 中唯一存在的强制转换,并且程序员始终是显式的,从而触发显式转换。还有各种隐式转换。但是没有隐式转换。 【参考方案1】:

你的问题一点也不傻。您已经接近解决方案:i 被赋值为 -3,但隐式转换为 iunsigned char 的类型会将值更改为 253

为了更准确的解释,您的测试代码中有多个问题:

char 可能是有符号或无符号的,具体取决于平台和编译器配置,因此char cnt = -1; 可以将值-1255 存储到cnt 中,或者如果char 是无符号的,甚至可以存储其他值并且有超过 8 位。

for (i = cnt - 2; i &lt; cnt; i--) 的行为还取决于默认情况下char 是有符号还是无符号:

在所有情况下,都会对测试 i &lt; cnt 进行评估,同时将两个操作数都转换为 int(或 unsigned int,在极少数情况下是 sizeof(int)==1)。如果int 可以表示charunsigned char 类型的所有值,则此转换不会更改值。

如果char 是无符号的并且有8 位,cnt 的值是255,所以i 用值253 初始化,循环运行254 次,i253 向下到0,然后i-- 将值255 再次存储到i,测试i &lt; cnt 评估为假。循环打印507,然后是759,...32385

如果char 是有符号的并且有8 位,就像您的系统上可能的情况一样,cnt 的值是-1 并且i 被初始化为值-3 转换为unsigned char,这是253。初始测试i &lt; cnt 评估为253 &lt; -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

【讨论】:

以上是关于有符号和无符号字符之间的比较的主要内容,如果未能解决你的问题,请参考以下文章

c中的有符号字符和无符号字符之间的区别

为啥在有符号和无符号表示之间转换一个数字?

Arduino 在 uint32_t 和无符号字符之间转换

有符号/无符号字符之间的区别[重复]

有符号和无符号之间的减法,然后是除法

SWIG C/python 和无符号字符指针