为什么补充行为不同通过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 char
types的一个对象被提升为(signed) int
,这是printf
函数使用的(在~
操作符求值之后)匹配的%d
格式说明符。
请注意, 缺省参数促销 (如printf
是一个可变参数函数)在这里不起任何作用,因为对象已经是int
types了。
另一方面,在这个代码中:
unsigned char c = 4, d; d = ~c; printf("%d", d);
会发生以下步骤:
- 由于
~
(以同样的方式,如上所述) - 〜c rvalue被评估为(signed)
int
值(例如-5
) -
d=~c
〜c从int
到unsigned char
进行隐式转换,因为d
有这样的types。 你可以把它想成和d = (unsigned char) ~c
。 请注意,d
不能是负数(这是所有未签名types的一般规则)。 -
printf("%d", d);
调用默认参数促销 ,因此d
被转换为int
并且(非负)值被保留(即int
types可以表示所有的unsigned char
types的值)。
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) 。
在第二个片段中的操作~
之前, char
在printf
语句中被提升为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
c
到int
的默认提升也在
d = ~c;
在补码操作之前,结果被转换回unsigned char
因为d
是unsigned 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一元算术运算符
~
运算符的结果是其(提升的)操作数的按位补数(也就是说,当且仅当转换的操作数中的相应位未被置位时,结果中的每一位都被置位)。 整数提升在操作数上执行,结果具有提升的types 。 如果升级types是“无符号types”,则expression式~E
相当于该types中表示的最大值减E
“。
由于unsigned char
types比(因为它需要更less的字节)更窄( int
型), – 在编译时(在应用补充操作之前,由抽象机器(编译器)执行的隐式types提升和variablesc
值提升为int
)。 这是正确执行程序所必需的,因为需要一个整型操作数。
§6.5expression式
- 一些运算符(一元运算符
~
和二元运算符<<
,>>
,&
,^
和|
统称为按位运算符) 需要具有整数types的操作数 。 这些运算符产生取决于整数的内部表示的值,并且对于有符号types具有实现定义的和未定义的方面。
编译器足够聪明,可以分析expression式,检查expression式的语义,在需要时执行types检查和算术转换。 这就是在char
types上应用~
的原因,我们不需要显式地写入~(int)c
– 称为显式types转换(并避免错误)。
注意:
-
c
值在expression式int
中被提升为int
,但是c
types仍然是unsigned char
– 它的types不是。 不要混淆。 -
重要的是:
~
操作的结果是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一元算术运算符- 一元运算符的结果是其(提升)操作数的负值。 整数提升在操作数上执行, 结果具有提升的types。
现在,@haccks在他的回答中也解释了 – 在32位计算机上c〜4的值为〜c的结果是:
1111 1111 1111 1111 1111 1111 1111 1011
在十进制它是-5
– 这是你的第二个代码的输出!
在你的第一个代码中 ,另外一行有趣的是理解b = ~c;
,因为b
是一个unsigned char
variables,而unsigned char
结果是int
types的,所以为了适应〜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。