为什么补码通过printf表现不同?

时间:2015-02-17 10:50:22

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

我正在阅读关于按位运算符的章节,我遇到了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

为什么它不起作用?

6 个答案:

答案 0 :(得分:55)

在此声明中:

printf("%d",~c);

在应用 c(按位补码)运算符之前,int转换为~ 1 类型。这是因为整数提升,它们被调用到~的操作数。在这种情况下,unsigned char类型的对象会被提升为(已签名)int,然后~运算符评估后printf运算符使用{}}匹配%d 1}}格式说明符。

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

另一方面,在此代码中:

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

发生以下步骤:

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

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

答案 1 :(得分:27)

char在第二个代码段中的操作int之前的printf语句中被提升为~。所以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

请注意,char cint的默认促销也会在

中执行
d = ~c;

在补语操作之前,但结果将转换回unsigned chard的类型为unsigned char

C11:6.5.16.1简单赋值(p2):

  

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

6.5.16(p3):

  

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

答案 2 :(得分:17)

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

  

§ 6.5.3.3 Unary arithmetic operators

     
      
  1. ~运算符的结果是它的按位补码   (提升)操作数(也就是说,结果中的每个位都是设置的   如果未设置转换后的操作数中的相应位)。的的   对操作数执行整数提升,结果为   提升类型。如果推广类型是" '无符号类型',   表达式~E等效于其中可表示的最大值   类型减去E"。
  2.   

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

  

§ 6.5 Expressions

     
      
  1. 一些运算符(一元运算符~和二元运算符<<>>&^,和|,   统称为按位运算符)需要具有的操作数   整数类型。这些运算符产生的值取决于内部表示   整数,并且已签名类型具有实现定义和未定义的方面。
  2.   

编译器非常智能 - 足以分析表达式,检查表达式的语义,执行类型检查和算术转换(如果需要)。这就是在~类型上应用char的原因,我们不需要明确地编写~(int)c - 称为显式类型转换(并避免错误)。< / p>

注意:

  1. c的值已升级为int ~c,但c的类型仍为unsigned char - 其类型不是~。不要困惑。

  2. 重要提示: int操作的结果属于#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; } 类型!,请检查以下代码(我没有vs-compiler,我是使用gcc):

    $ 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. 一元~c运算符的结果是其(提升的)操作数的否定。整数   促销是在操作数上执行的,结果具有提升类型。
    2.   
  3. 现在,正如@haccks在他的answer中解释的那样 - 在32位机器上c = 4的结果和1111 1111 1111 1111 1111 1111 1111 1011 的值是:

    -5

    十进制为b = ~c; - 这是第二个代码的输出!

    第一个代码中,要了解b,还有一行很有意思,因为unsigned char~c变量,int的结果属于~c类型,因此为了适应b 1111 1111 1111 1111 1111 1111 1111 1011 // -5 & 0xFF & 0000 0000 0000 0000 0000 0000 1111 1111 // - one byte ------------------------------------------- 1111 1011 结果值(~c)的结果值,被截断以适合无符号字符类型 as如下:

    1111 1011

    251的十进制等值为printf("\n ~c = %d", ~c & 0xFF); 。您可以使用以下方式获得相同的效果:

    {{1}}

    或@ouah在answer中使用显式强制转换的建议。

答案 3 :(得分:12)

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

然后

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

答案 4 :(得分:10)

  

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

而不是:

printf("%d",~c);

使用:

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

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

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

答案 5 :(得分:8)

整数推广,来自标准:

  

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