为什么补充行为不同通过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从int到unsigned 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) 。
在第二个片段中的操作~之前, 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 chartypes比(因为它需要更less的字节)更窄( int型), – 在编译时(在应用补充操作之前,由抽象机器(编译器)执行的隐式types提升和variablesc值提升为int )。 这是正确执行程序所必需的,因为需要一个整型操作数。
§6.5expression式
- 一些运算符(一元运算符
~和二元运算符<<,>>,&,^和|统称为按位运算符) 需要具有整数types的操作数 。 这些运算符产生取决于整数的内部表示的值,并且对于有符号types具有实现定义的和未定义的方面。
编译器足够聪明,可以分析expression式,检查expression式的语义,在需要时执行types检查和算术转换。 这就是在chartypes上应用~的原因,我们不需要显式地写入~(int)c – 称为显式types转换(并避免错误)。
注意:
-
c值在expression式int中被提升为int,但是ctypes仍然是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 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。