【问题标题】:Why does the complement behave differently through printf?为什么补码通过 printf 表现不同?
【发布时间】:2015-04-18 01:47:37
【问题描述】:

我正在阅读有关位运算符的一章,我遇到了 1 的补码运算符程序并决定在 Visual C++ 上运行它。

int main ()
{
   unsigned char c = 4, d;
   d = ~c;
   printf("%d\n", d);
}

它给出了有效的输出:251

然后我决定不使用d作为变量来保存~c的值,而是直接打印~c的值。

int main ()
{
   unsigned char c=4;
   printf("%d\n", ~c);
}

它给出了输出-5

为什么没用?

【问题讨论】:

  • 提示:4 的 1 的补码等价于 -5 的二进制补码表示
  • complement,不是compliment
  • @LưuVĩnhPhúc 你的补充值得称赞。

标签: c variables types bitwise-operators unsigned-char


【解决方案1】:

在此声明中:

printf("%d",~c);

c 被转换为int1 类型 before ~(按位补码)运算符被应用。这是因为 整数提升,它被调用到 ~ 的操作数。在这种情况下,unsigned char 类型的对象被提升为(有符号)int,然后(在 ~ 运算符评估之后)由printf 函数使用,并匹配%d 格式说明符。

请注意,默认参数提升(因为printf 是一个可变参数函数)在这里没有任何作用,因为对象已经是int 类型。

另一方面,在这段代码中:

unsigned char c = 4, d;
d = ~c;
printf("%d", d);

发生以下步骤:

  • cinteger Promotions 的主题,因为 ~ (以同样的方式,如上所述)
  • ~c 右值被评估为(有符号)int 值(例如-5
  • d=~c 进行从 intunsigned char 的隐式转换,因为 d 具有这种类型。您可能认为它与d = (unsigned char) ~c 相同。请注意,d 不能为负数(这是所有无符号类型的一般规则)。
  • printf("%d", d); 调用默认参数提升,因此d 被转换为int 并保留(非负)值(即int 类型可以表示unsigned char 类型的所有值) .

1) 假设int 可以表示unsigned char 的所有值(请参阅下面的TC 的comment),但是非常可能以这种方式发生.更具体地说,我们假设INT_MAX >= UCHAR_MAX 成立。通常sizeof(int) > sizeof(unsigned char) 持有并且字节由八位组成。否则,c 将转换为 unsigned int(如 C11 子条款 §6.3.1.1/p2),格式说明符也应相应更改为 %u 以避免获得 UB(C11 §7.21. 6.1/p9)。

【讨论】:

  • 但这也发生在d=~c。真正的区别是在d=~c 中发生的(返回)到unsigend char 的转换,而不是在printf 调用中。
  • @MarcvanLeeuwen:d 的类型为 unsigned char,因此它不能为负数(即使在转换为 int 之后,因为 printf默认参数提升我>)。事实上,你是对的,在d=~c 中也发生了整数提升(以同样的方式,正如我在上面的答案中描述的那样),但是赋值又将int 转换为unsigned char。另一方面,在第二种情况下,printf 函数采用“原样”int 参数(即%d 格式说明符是正确的),所以 default arguments Promotions 在那里什么都不做。
  • 对于更多读者:我更新了我的答案以反映 Marc 的观察。您可以在修订历史记录中找到原始(较短的)答案。希望现在一切都清楚了。
  • 请注意,在某些系统中,unsigned char 可以通过整数提升(例如,如果 sizeof(int) == 1)提升为 unsigned int,在这种情况下,printf 将调用未定义的行为,因为它使用错误的说明符。
  • @T.C.:你是对的,但让我们假设(为简单起见)我们生活在一个字节由八位组成的世界。我知道标准没有强制执行它,但例如 POSIX 执行。
【解决方案2】:

~ 运算符应用于c 时,它会提升为int,结果也是int

然后

  • 在第一个示例中,结果被转换为unsigned char,然后提升为signed int 并打印出来。
  • 在第二个示例中,结果打印为signed int

【讨论】:

    【解决方案3】:

    整数提升,来自标准:

    如果带符号整数类型的操作数的类型可以表示所有 无符号整数类型的操作数类型的值, 无符号整数类型的操作数应转换为类型 带符号整数类型的操作数。

    【讨论】:

      【解决方案4】:

      char 在第二个 sn-p 中的操作 ~ 之前在 printf 语句中提升为 int。所以c,就是

      0000 0100 (2's complement)  
      

      二进制提升为(假设是32位机器)

      0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x  
      

      其按位补码等于该值的二进制补码减一 (~x = −x − 1)

      1111 1111 1111 1111 1111 1111 1111 1011  
      

      这是-5 2 的补码形式的十进制。

      注意charcint的默认提升也是在

      d = ~c;
      

      在补码运算之前,但结果被转换回unsigned char,因为d 的类型为unsigned char

      C11:6.5.16.1 简单赋值(p2):

      在简单赋值(=)中,右操作数的值被转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。

      6.5.16 (p3):

      赋值表达式的类型是左操作数的类型 左值转换后。

      【讨论】:

        【解决方案5】:

        它给出了 op -5。为什么它不起作用?

        代替:

        printf("%d",~c);
        

        使用:

        printf("%d", (unsigned char) ~c);
        

        获得与第一个示例相同的结果。

        ~ 操作数经过整数提升,默认参数提升应用于可变参数函数的参数。

        【讨论】:

          【解决方案6】:

          要了解代码的行为,您需要了解名为 'Integer Promotions' 的概念(在您的代码中隐式发生在对 unsigned char 操作数进行位非操作之前)如 N1570 委员会草案中所述:

          § 6.5.3.3 Unary arithmetic operators

          1. ~ 运算符的结果是它的按位补码 (提升)操作数(即,结果中的每一位都被设置当且仅 如果未设置转换后的操作数中的相应位)。 该 对操作数执行整数提升,结果有 提升的类型。如果提升的类型是“‘无符号类型’,则 表达式~E 等价于可表示的最大值 输入减号E"。

          因为unsigned char 类型比int 类型更窄(因为它需要更少的字节), - 由抽象机(编译器)执行的隐式类型提升和变量c 的值在当时被提升为int编译(在应用补码操作~ 之前)。它是正确执行程序所必需的,因为~ 需要一个整数操作数。

          § 6.5 Expressions

          1. 一些运算符(一元运算符~和二元运算符<<>>&^|, 统称为按位运算符)必须有操作数 整数类型。这些运算符产生的值取决于 整数,并且有符号类型的实现定义和未定义方面。

          编译器足够聪明,可以分析表达式、检查表达式的语义、执行类型检查和算术转换(如果需要)。这就是在 char 类型上应用 ~ 的原因,我们不需要显式编写 ~(int)c — 称为显式类型转换(并且要避免错误)。

          注意:

          1. c 的值在表达式 ~c 中提升为 int,但 c 的类型仍然是 unsigned char - 它的类型不是。不要混淆。

          2. 重要提示:~ 操作的结果是int 类型!,查看以下代码(我没有 vs-compiler,我使用的是 gcc):

            #include<stdio.h>
            #include<stdlib.h>
            int main(void){
               unsigned char c = 4;
               printf(" sizeof(int) = %zu,\n sizeof(unsigned char) = %zu",
                        sizeof(int),
                        sizeof(unsigned char));
               printf("\n sizeof(~c) = %zu", sizeof(~c));        
               printf("\n");
               return EXIT_SUCCESS;
            }
            

            编译并运行:

            $ gcc -std=gnu99 -Wall -pedantic x.c -o x
            $ ./x
            sizeof(int) = 4,
            sizeof(unsigned char) = 1
            sizeof(~c) = 4
            

            注意~c 的结果大小与int 相同,但不等于unsigned char — 此表达式中~ 运算符的结果为int!正如提到的6.5.3.3 Unary arithmetic operators

            1. 一元- 运算符的结果是其(提升的)操作数的负数。整数 对操作数执行提升,并且结果具有提升的类型。

          现在,正如@hacks 在他的answer 中所解释的那样,~c 在 32 位机器上的结果和c = 4 的值是:

          1111 1111 1111 1111 1111 1111 1111 1011
          

          十进制是-5——这是你的第二个代码的输出!

          在您的第一个代码中,还有一行很有趣,可以理解b = ~c;,因为b 是一个unsigned char 变量,~c 的结果是int 类型,所以为了适应 ~cb 的结果值,结果值 (~c) 被截断以适合 unsigned char 类型,如下所示:

              1111 1111 1111 1111 1111 1111 1111 1011  // -5 & 0xFF
           &  0000 0000 0000 0000 0000 0000 1111 1111  // - one byte      
              -------------------------------------------          
                                            1111 1011  
          

          1111 1011 的十进制等效值为 251。您可以使用以下方法获得相同的效果:

          printf("\n ~c = %d", ~c  & 0xFF); 
          

          或如@ouah 在他的answer 中建议的那样使用显式转换。

          【讨论】:

          • @hacks 谢谢,是的,我来这里主要是为了学习而不是参与,目前我主要从事 python、django、web 工作。
          • +int(PI/3) 用于实际引用相关规范部分......没有其他答案实际上解释了为什么促销发生在这里! this->Some operators (the unary operator ~, and the binary operators &lt;&lt;, &gt;&gt;, &amp;, ^, and |, collectively described as bitwise operators) are required to have operands that have integer type. 今天让我很开心。
          • @vaxquis 谢谢,-当我来到这篇文章时,我注意到大多数答案都在解释“结果如何不同”,所以我决定添加我的答案并强调“为什么这样”的原因, -- 在您阅读我的回答时,我在草稿中遗漏了另一个相关内容,现已添加。
          猜你喜欢
          • 1970-01-01
          • 2012-06-03
          • 2021-04-07
          • 1970-01-01
          • 2016-02-22
          • 2019-08-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多