signed 和 unsigned char 之间的比较

comparison between signed and unsigned char

我几乎认为这是一个愚蠢的问题...但我真的找不到答案。所以我在这里问这个。

出于学习隐式类型转换的目的,我运行以下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-loop的循环条件为false,所以程序直接退出了for-loop

但是,我不明白为什么。

据我所知,C 在分配或比较不同类型的变量时会进行隐式转换。所以我想在i = cnt - 2上,减号操作使值-3,然后隐式转换为i赋值253。

那么,条件 i < cnt 不应该为真,因为(由于有符号和无符号字符的比较,通过 cnt 的另一个隐式转换)253 小于 255?

如果不是,为什么这是错误的?有没有我遗漏的东西或者有什么我不知道的例外情况?

对于初学者,我们假设类型 char 的行为与类型相同 signed char.

在这种情况下

i < cnt

由于整数提升,两个操作数都隐式转换为类型 int

来自 C 标准(6.5.8 关系运算符)

3 If both of the operands have arithmetic type, the usual arithmetic conversions are performed.

and (6.3.1.8 常用算术转换)

1 Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result. For the specified operands, each operand is converted, without change of type domain, to a type whose corresponding real type is the common real type. Unless explicitly stated otherwise, the common real type is also the corresponding real type of the result, whose type domain is the type domain of the operands if they are the same, and complex otherwise. This pattern is called the usual arithmetic conversions:

... Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:

If both operands have the same type, then no further conversion is needed

and (6.3.1.1 Boolean, characters, and integers)

  1. ...If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.

因此 i 的正值在整数提升后表示为 0000 0000 1111 1101 将大于负值 1111 1111 1111 1111.

因此 for 循环的条件立即计算为逻辑假,因为类型 int 的正值 253 大于类型 -1 的负值=15=].

这是一个演示程序。

#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

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

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

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

  • for (i = cnt - 2; i < cnt; i--)的行为还取决于char默认是有符号还是无符号:

    • 在所有情况下,测试 i < cnt 被评估为两个操作数都转换为 int(或 unsigned intsizeof(int)==1 的罕见情况下).如果 int 可以表示类型 charunsigned char 的所有值,则此转换不会更改值。

    • 如果 char 是无符号的并且有 8 位,cnt 的值为 255 所以 i 被初始化为值 253 并且循环运行 254 次 i253 下降到 0,然后 i-- 再次将值 255 存储到 i,测试 i < cnt 的计算结果为 false。循环打印 507,然后 759,... 32385.

    • 如果 char 已签名且有 8 位,就像您的系统可能的情况一样,cnt 的值为 -1i 初始化为值 -3 转换为 unsigned char,即 253。初始测试 i < cnt 的计算结果为 253 < -1,这是错误的,导致立即跳过循环体。

您可以通过为编译器提供适当的标志(例如:gcc -funsigned-char)强制 char 默认无符号,并测试行为如何变化。使用 Godbolt's compiler explorer,您可以看到 gcc 在有符号(默认)情况下仅生成 2 条指令到 return 0,在无符号情况下生成预期输出。