我正在阅读有关按位运算符的一章,我遇到了 1 的补码运算符程序,并决定在 Visual C++上运行它。
int main ()
{
unsigned char c = 4, d;
d = ~c;
printf("%dn", d);
}
它给出有效的输出:251
然后我决定直接打印~c
的值,而不是用d
作为变量来保存~c
的值。
int main ()
{
unsigned char c=4;
printf("%dn", ~c);
}
它给出输出-5
.
为什么没有用?
在此语句中:
printf("%d",~c);
在应用~
(按位补码(运算符之前,c
将转换为 int
1 类型。这是因为整数提升,这些提升被调用到~
的操作数。在这种情况下,unsigned char
类型的对象被提升为(有符号的(int
,然后(在~
运算符计算之后(由printf
函数使用,并匹配%d
格式说明符。
请注意,默认参数提升(因为printf
是一个可变参数函数(在这里没有任何作用,因为对象已经是 int
类型。
另一方面,在此代码中:
unsigned char c = 4, d;
d = ~c;
printf("%d", d);
将发生以下步骤:
- 由于
~
,c
是整数促销的对象(如上所述( -
~c
右值被评估为(有符号(int
值(例如-5
( -
d=~c
从int
隐式转换为unsigned char
,因为d
有这样的类型。您可能会认为它与d = (unsigned char) ~c
相同。请注意,d
不能为负数(这是所有无符号类型的一般规则(。 -
printf("%d", d);
调用默认参数提升,因此d
被转换为int
并保留(非负(值(即int
类型可以表示unsigned char
类型的所有值(。
1(假设int
可以代表unsigned char
的所有值(参见下面的T.C.评论(,但它很可能以这种方式发生。更具体地说,我们假设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(。
char
在第二个代码段中操作~
之前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
以 2 的补码形式以十进制-5
。
请注意,默认将char
c
提升为int
d = ~c;
在补码操作之前,但结果被转换回unsigned char
因为d
类型为 unsigned char
。
C11: 6.5.16.1 简单赋值 (p2(:
在简单赋值(
=
(中,右操作数的值被转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。
和
6.5.16(第3页(:
赋值表达式的类型是左操作数的类型左值转换后。
要了解代码的行为,您需要学习称为"整数提升"的概念(在对unsigned char
操作数进行按位 NOT 操作之前隐式发生在代码中( 如 N1570 委员会草案中所述:
§ 6.5.3.3 一元算术运算符
~
运算符的结果是其 (提升的(操作数(即,结果中的每个位都设置为 当且仅 如果未设置转换操作数中的相应位(。这 对操作数执行整数提升,结果具有 升级的类型。如果升级的类型是"无符号类型",则 表达式~E
等效于其中可表示的最大值 键入减号E
"。
由于unsigned char
类型比int
类型窄(因为它需要的字节更少(,因此由抽象机器(编译器(执行的隐式类型提升和变量c
的值在编译时(在应用补码操作之前(~
被提升为int
。它是正确执行程序所必需的,因为~
需要一个整数操作数。
§ 6.5 表达式
- 一些运算符(一元运算符
~
,二元运算符<<
、>>
、&
、^
和|
, 统称为按位运算符(需要具有具有 整数类型。这些运算符生成的值取决于 整数,并且具有有符号类型的实现定义和未定义方面。
编译器足够聪明,可以分析表达式,检查表达式的语义,在需要时执行类型检查和算术转换。这就是为什么要char
类型应用~
,我们不需要显式编写~(int)c
- 称为显式类型转换(并且确实避免错误(。
注意:
-
c
的值在表达式~c
中提升为int
,但c
的类型仍然unsigned char
- 其类型不。 不要混淆。 -
重要提示:
~
操作的结果是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 x.c -o x $ ./x sizeof(int) = 4, sizeof(unsigned char) = 1 sizeof(~c) = 4
注意:
~c
的结果大小与int
相同,但不等于unsigned char
— 此表达式中~
运算符的结果为int
! 如前所述 6.5.3.3 一元算术运算符- 一元
-
运算符的结果是其(提升的(操作数的负数。整数 升级对操作数执行,结果具有升级类型。
- 一元
现在,正如@haccks在他的回答中也解释的那样 - 在 32 位机器上~c
的结果和 c = 4
的值是:
1111 1111 1111 1111 1111 1111 1111 1011
在十进制中,它是-5
— 这是您的第二个代码的输出!
在你的第一个代码中,还有一行很有趣,理解b = ~c;
,因为b
是一个unsigned char
变量,~c
的结果是int
类型,所以为了适应~c
结果的值b
结果值(~c(被截断以适应无符号字符类型,如下所示:
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在他的回答中建议的那样,使用显式强制转换。
当应用 ~
运算符c
它被提升为 int
时,结果也是一个int
。
然后
- 在第一个示例中,结果被转换为
unsigned char
,然后提升为signed int
并打印。 - 在第二个示例中,结果打印为
signed int
。
它给出了 op -5。 为什么它不起作用?
而不是:
printf("%d",~c);
用:
printf("%d", (unsigned char) ~c);
以获得与第一个示例中相同的结果。
~
操作数经历整数提升,默认参数提升应用于可变参数函数的参数。
整数提升,来自标准:
如果具有有符号整数类型的操作数的类型可以表示所有 无符号整数类型的操作数类型的值, 具有无符号整数类型的操作数应转换为 具有有符号整数类型的操作数。