为什么补充行为不同通过printf?

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

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

它给出了有效的输出: 251

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

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

它给出-5的输出。

为什么它不工作?

在这个声明中:

 printf("%d",~c); 

在应用 ~ (按位互补)运算符之前,c转换为int 1types。 这是因为整型促销 ,被调用到~操作数。 在这种情况下, unsigned chartypes的一个对象被提升为(signed) int ,这是printf函数使用的(在~操作符求值之后)匹配的%d格式说明符。

请注意, 缺省参数促销 (如printf是一个可变参数函数)在这里不起任何作用,因为对象已经是inttypes了。

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

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

会发生以下步骤:

  • 由于~ (以同样的方式,如上所述)
  • 〜c rvalue被评估为(signed) int值(例如-5
  • d=~c 〜c从intunsigned char进行隐式转换,因为d有这样的types。 你可以把它想成和d = (unsigned char) ~c 。 请注意, d不能是负数(这是所有未签名types的一般规则)。
  • printf("%d", d); 调用默认参数促销 ,因此d被转换为int并且(非负)值被保留(即inttypes可以表示所有的unsigned chartypes的值)。

1)假设int可以表示所有的unsigned char值(参见TC的注释 ),但这种情况可能发生。 更具体地说,我们假设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) 。

在第二个片段中的操作~之前, charprintf语句中被提升为int 。 所以c ,这是

 0000 0100 (2's complement) 

在二进制升级(假设32位机器)

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

并且它的逐位补码等于值的二进制补码减1( ~x = −x − 1

 1111 1111 1111 1111 1111 1111 1111 1011 

在2的补码forms中是十进制的-5

请注意, char cint的默认提升也在

 d = ~c; 

在补码操作之前,结果被转换回unsigned char因为dunsigned char

C11:6.5.16.1简单赋值(p2):

在简单赋值( = )中,右操作数的值被转换为赋值expression式的types,并replace左操作数指定的对象中存储的值。

6.5.16(p3):

赋值expression式的types是左值操作数在左值转换后的types。

为了理解你的代码的行为,你需要学习一个叫做“Integer Promotions”的概念(在你的代码中隐式地发生在位操作unsigned char操作数之前)如N1570委员会草案所述:

§6.5.3.3一元算术运算符

  1. ~运算符的结果是其(提升的)操作数的按位补数(也就是说,当且仅当转换的操作数中的相应位未被置位时,结果中的每一位都被置位)。 整数提升在操作数上执行,结果具有提升的types 。 如果升级types是“无符号types”,则expression式~E相当于该types中表示的最大值减E “。

由于unsigned chartypes比(因为它需要更less的字节)更窄( int型), – 在编译时(在应用补充操作之前,由抽象机器(编译器)执行的隐式types提升和variablesc值提升为int )。 这是正确执行程序所必需的,因为需要一个整型操作数。

§6.5expression式

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

编译器足够聪明,可以分析expression式,检查expression式的语义,在需要时执行types检查和算术转换。 这就是在chartypes上应用~的原因,我们不需要显式地写入~(int)c – 称为显式types转换(并避免错误)。

注意:

  1. c值在expression式int中被提升为int ,但是ctypes仍然是unsigned char – 它的types不是。 不要混淆。

  2. 重要的是: ~操作的结果是int型的,检查下面的代码(我没有vs编译器,我正在使用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 xc -ox $ ./x sizeof(int) = 4, sizeof(unsigned char) = 1 sizeof(~c) = 4 

    注意 :〜c的结果大小与int相同,但不等于unsigned char – 该expression式中~运算符的结果是int ! 如上所述6.5.3.3一元算术运算符

    1. 一元运算符的结果是其(提升)操作数的负值。 整数提升在操作数上执行, 结果具有提升的types。

现在,@haccks在他的回答中也解释了 – 在32位计算机上c〜4的值为〜c的结果是:

 1111 1111 1111 1111 1111 1111 1111 1011 

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

在你的第一个代码中 ,另外一行有趣的是理解b = ~c; ,因为b是一个unsigned charvariables,而unsigned char结果是inttypes的,所以为了适应〜c结果的值,结果值(〜c)被截断以符合unsigned chartypes ,如下所示:

  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在他的回答中所build议的那样使用明确的转换。

当将~操作符应用于c它被提升为int ,结果也是一个int

然后

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

它给了op -5。 为什么它不工作?

代替:

 printf("%d",~c); 

使用:

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

得到与你的第一个例子相同的结果。

〜operand进行整数提升,默认参数提升应用于可变参数的自variables。

整数提升,从标准:

如果具有有符号整数types的操作数的types可以表示具有无符号整数types的操作数types的所有值,则将具有无符号整数types的操作数转换为有符号整数types的操作数的types。