为什么用 %d 打印字符变量会在 c 中给出负值?

Why does printing a character variable with %d give a negative value in c?

我尝试了以下代码,期望输出为正 64:

char val = 0x80; 
printf("%d",val>>1);

我对发生的事情的理解是(如果我错了,请纠正我):

  1. 参考 ASCII table,没有 0x80 到任何字符的映射,因此我假设它存储为无符号整数。
  2. 这在按位格式中表示为 1000 0000,因此右移 1 将导致 0100 0000
  3. 当打印为整数值时,这将显示为正 64。

但是它显​​示-64。

对比:

char val = 0x40; 
printf("%d",val>>1);

给出正数 32。

是否在第一种情况下将值隐式转换为有符号整数,而在第二种情况下没有?

有符号整数的右移是实现定义的。在大多数现代系统中,有符号整数是二进制补码,编译器会将移位转换为 算术 移位。

移位后val的二进制值为0xc0,即二进制补码编码中的-64

首先将val转换为有符号整数,然后传递给函数。如果您对您的问题付出一些努力并在您的代码中添加几行,您就会自己发现它。

int main(void)
{
    char c = 0x80;

    printf("%d\n", c >> 1);
    printf("%x\n", c >> 1);
    printf("%hhd\n", c >> 1);
    printf("%hhx\n", c >> 1);

    c >>= 1;

    printf("%d\n", c);
    printf("%x\n", c);
    printf("%hhd\n",c);
    printf("%hhx\n",c);
}

https://godbolt.org/z/YsaGos

您还可以查看 MSB 位是否为 0 算术移位的行为与二进制移位完全相同,因此 0x40 >> 1 == 0x20

您的 C 实现使用八位签名 char。 (C 标准允许 char 有符号或无符号。)在 char val = 0x80; 中,char 不能代表你初始化它的值,128。在这种情况下,值 128 被转换到 char,根据 C 2018 6.3.1.3 3,它会产生一个实现定义的值或一个陷阱。您的实施可能会产生 −128。 (这是一个常见的结果,因为 128 在二进制中是 10000000,将超出范围的结果转换为八位二进制补码整数通常只是将值的低八位重新解释为八位二进制补码。在二进制补码中, 10000000 代表 −128.)

所以val>>1要求将−128右移一位。根据 C 2018 6.5.7 5,将负值右移会产生实现定义的值。产生 −64 是一个常见的结果。

(详细地说,在 val>>1 中,val 自动从 char 提升到 int。它具有相同的值,即 −128。但是,使用32 位 int,然后它将表示为 11111111111111111111111110000000 而不是 10000000。然后“算术”右移,传播符号位,产生 11111111111111111111111111000000,这是 -64,你可能会得到一些 C 实现的结果。 “逻辑上”右移,将符号位设置为零,得到 0111111111111111111111111000000。在这种情况下,printf 将显示“2147483584”,即 231−64) .

ASCII是否有任何代码为0x80的字符是无关紧要的。 C 规则适用于所涉及的值,无论使用何种字符编码方案。