【问题标题】:Why does printing a character variable with %d give a negative value in c?为什么用 %d 打印字符变量会在 c 中给出负值?
【发布时间】:2021-03-10 21:59:13
【问题描述】:

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

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

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

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

但是显示-64。

相比之下:

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

给出正值 32。

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

【问题讨论】:

  • 如果char 在您的实现中签名,则(int)(char)0x80 的值更像二进制1111 1111 1111 1111 1111 1111 1000 0000
  • so i assume this is stored as an unsigned integer. - 很可能,您使用的 char 和隐含的 int 都没有签名。如果您想要未签名的行为,请明确说明。
  • char 的类型不会根据您在其中存储的内容而改变。如果char 被签名,那么0x80 也是-128。如果char 被签名,那么val>>1 要么是未定义的,要么是实现定义的(不记得是哪个),但是回到输出-64 那么你的char 不能是未签名的。
  • char 类型在传递给像 printf() 这样的可变参数函数时会提升为 int。它也被提升为int 以执行轮班。由于有符号的int 可以保存charsigned charunsigned char 的所有可能值,因此无论char 的符号性如何,转换后的值都是int 类型。

标签: c bit-manipulation placeholder signed shift


【解决方案1】:

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

移位后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

【讨论】:

    【解决方案2】:

    您的 C 实现使用 8 位有符号 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,它会表示为 1111111111111111111111111111111111111111111111111110000000 而不是 10000000。然后“算术”右移,传播符号位,产生 11111111111111111111111111000000,即 -64,即结果。一些 C 实现可能会“逻辑地”右移,从而设置符号位零,产生 01111111111111111111111111000000。在这种情况下,printf 将显示“2147483584”,即 231-64)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-08
      • 2018-01-29
      • 2011-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-02
      • 2023-03-28
      相关资源
      最近更新 更多