Printf函数格式化程序

具有以下简单的C++代码:

#include <stdio.h>

int main() {
    char c1 = 130;
    unsigned char c2 = 130;

    printf("1: %+u
", c1);
    printf("2: %+u
", c2);
    printf("3: %+d
", c1);
    printf("4: %+d
", c2);
    ...
    return 0;
}

输出如下:

1: 4294967170
2: 130
3: -126
4: +130

有人能给我解释一下第一行和第三行的结果吗?

我使用的是具有所有默认设置的Linuxgcc编译器。


解决方案

Achar为8位。这意味着它可以表示2^8=256个唯一值。uchar表示0到255,带符号的char表示-128到127(绝对可以表示任何东西,但这是典型的平台实现)。因此,将130赋给char超出范围2,当解释为带符号的char时,该值溢出并将值换行为-126。编译器将130视为整数,并进行从intchar的隐式转换。在大多数平台上,int是32位的,符号位是MSB,值130很容易放入前8位,但是然后编译器想要砍掉24位以将其压缩成一个字符。当这种情况发生时,并且您已经告诉编译器您需要一个带符号的字符,那么前8位的MSB实际上表示-128。啊哦!您现在的内存中有这个1000 0010,当解释为有符号字符时,它是-128+2。我的平台上的Linter对此尖叫着。。

我提出关于解释的重要一点是因为在内存中,这两个值是相同的。您可以通过在printf语句中强制转换值来确认这一点,即printf("3: %+d ", (unsigned char)c1);,您将再次看到130。

您在第一个printf语句中看到大值的原因是,您将有符号的char转换为无符号的int,其中char已经溢出。计算机首先将char解释为-126,然后强制转换为不能表示负值无符号int,因此您将获得有符号int的最大值并减去126。

2^32-126=4294967170。。宾果游戏

printf语句2中,机器所要做的就是添加24个0以达到32位,然后将该值解释为int。在语句1中,您已经告诉它您有一个带符号的值,所以它首先将其转换为32位-126值,然后将-ve整数解释为无符号整数。再一次,它颠倒了解释最重要位的方式。有2个步骤:

  1. 带符号的字符被提升为带符号的int,因为您希望使用int。字符(可能是复制的)添加了24位。因为我们看到的是带符号的值,所以某些机器指令会碰巧执行二进制补码,所以这里的内存看起来非常不同。
  2. 新的有符号整型内存被解释为无符号的,因此计算机查看MSB并将其解释为2^32,而不是升级中发生的-2^31。
有趣的是,如果您这样做char c1 = 130u;,您可以取消整齐的Linter警告,但是根据上面的逻辑,您仍然会得到相同的垃圾(即隐式转换丢弃了前24位,而符号位无论如何都是零)。我已经提交了一份基于探索此问题的llvm整齐的功能缺失报告(问题42137,如果您真的想关注它)??。

相关文章