简体   繁体   English

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

[英]Why does the complement behave differently through printf?

I was reading a chapter on bitwise operators, I came across 1's complement operator program and decided to run it on Visual C++. 我正在阅读关于按位运算符的章节,我遇到了1的补码运算符程序,并决定在Visual C ++上运行它。

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

It gives the valid output: 251 它给出了有效的输出: 251

Then instead of using d as a variable to hold the value of ~c , I decided to directly print the value of ~c . 然后,而不是使用d为变量来保存的值~c ,我决定直接打印的值~c

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

It gives the output -5 . 它给出了输出-5

Why didn't it work? 它为什么不起作用?

In this statement: 在这个声明中:

printf("%d",~c);

the c is converted to int 1 type before ~ (bitwise complement) operator is applied. 在应用 ~ (按位补码)运算符之前c被转换为int 1类型。 This is because of integer promotions , that are invoked to operand of the ~ . 这是因为整数提升 ,被调用到~操作数。 In this case an object of unsigned char type is promoted to (signed) int , which is then (after ~ operator evaluation) used by printf function, with matching %d format specifier. 在这种情况下, unsigned char类型的对象被提升为(signed) int ,然后(在~运算符求值之后)由printf函数使用,匹配%d格式说明符。

Notice that default argument promotions (as printf is a variadic function) does not play any role here, as object is already of type int . 请注意, 默认参数提升 (因为printf是一个可变参数函数)在这里不起任何作用,因为对象已经是int类型。

On the other hand, in this code: 另一方面,在这段代码中:

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

the following steps occur: 发生以下步骤:

  • c is a subject to integer promotions because of ~ (in the same way, as described above) 由于~ (以同样的方式,如上所述) c整数促销的主题
  • ~c rvalue is evaluated as (signed) int value (eg -5 ) ~c rvalue被评估为(有符号) int值(例如-5
  • d=~c makes an implicit conversion from int to unsigned char , as d has such type. d=~c进行从intunsigned char的隐式转换,因为d具有这种类型。 You may think of it as the same as d = (unsigned char) ~c . 您可能会认为它与d = (unsigned char) ~c Notice that d cannot be negative (this is general rule for all unsigned types). 请注意, d不能为负数(这是所有无符号类型的一般规则)。
  • printf("%d", d); invokes default argument promotions , thus d is converted to int and the (nonnegative) value is preserved (ie the int type can represent all values of unsigned char type). 调用默认参数提升 ,因此将d转换为int并保留(非负)值(即int类型可以表示unsigned char类型的所有值)。

1) assuming that int can represent all values of the unsigned char (see TC's comment below), but it is very likely to happen in this way. 1)假设int可以表示unsigned char所有值(参见下面的TC 注释 ),但可能以这种方式发生。 More specifically, we assume that INT_MAX >= UCHAR_MAX holds. 更具体地说,我们假设INT_MAX >= UCHAR_MAX成立。 Typically the sizeof(int) > sizeof(unsigned char) holds and byte consist of eight bits. 通常, sizeof(int) > sizeof(unsigned char)成立,byte由8位组成。 Otherwise the c would be converted to unsigned int (as by C11 subclause §6.3.1.1/p2), and the format specifier should be also changed accordingly to %u in order to avoid getting an UB (C11 §7.21.6.1/p9). 否则, c将被转换为unsigned int (如C11子条款§6.3.1.1/ p2所示),并且格式说明符也应相应地更改为%u ,以避免获得UB(C11§7.21.6.1/ p9) 。

char is promoted to int in printf statement before the operation ~ in second snippet. 在第二个代码段中的操作~之前, char被提升为printf语句中的int So c , which is 所以c ,这是

0000 0100 (2's complement)  

in binary is promoted to (assuming 32-bit machine) 在二进制中被提升为(假设32位机器)

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

and its bit-wise complement is equal to the two's complement of the value minus one ( ~x = −x − 1 ) 并且它的逐位补码等于值的二进制补码减一( ~x = −x − 1

1111 1111 1111 1111 1111 1111 1111 1011  

which is -5 in decimal in 2's complement form. 以2的补码形式,以十进制表示-5

Note that the default promotion of char c to int is also performed in 请注意,也将执行char cint的默认提升

d = ~c;

before complement operation but the result is converted back to unsigned char as d is of type unsigned char . 在补充操作之前,但结果将转换回unsigned char因为d的类型为unsigned char

C11: 6.5.16.1 Simple assignment (p2): C11:6.5.16.1简单分配(p2):

In simple assignment ( = ), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand. 在简单赋值( = )中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。

and

6.5.16 (p3): 6.5.16(p3):

The type of an assignment expression is the type the left operand would have after lvalue conversion. 赋值表达式的类型是左值操作数在左值转换后将具有的类型。

To understand behavior of your code, you need to learn the concept called 'Integer Promotions' (that happens in your code implicitly before bit wise NOT operation on an unsigned char operand) As mentioned in N1570 committee draft: 要理解代码的行为,你需要学习一个名为“整数促销”的概念(在你的代码中隐式地在unsigned char操作数上进行逐位运算之前发生)如N1570委员会草案所述:

§ 6.5.3.3 Unary arithmetic operators §6.5.3.3一元算术运算符

  1. The result of the ~ operator is the bitwise complement of its (promoted) operand (that is, each bit in the result is set if and only if the corresponding bit in the converted operand is not set). ~运算符的结果是其(提升的)操作数的按位补码(即,当且仅当未设置转换后的操作数中的相应位时,才会设置结果中的每个位)。 The integer promotions are performed on the operand, and the result has the promoted type . 整数提升在操作数上执行,结果具有提升类型 If the promoted type is an " 'unsigned type', the expression ~E is equivalent to the maximum value representable in that type minus E ". 如果提升类型是“'无符号类型',则表达式~E等于该类型中可表示的最大值减去E ”。

Because unsigned char type is narrower than (as it requires fewer bytes) int type, - implicit type promotion performed by abstract machine(compiler) and value of variable c is promoted to int at the time of compilation (before application of the complement operation ~ ). 因为unsigned char类型比(因为它需要更少的字节) int类型更窄, - 抽象机器(编译器)执行的隐式类型提升和变量c值在编译时被提升为int (在应用补码操作之前~ )。 It is required for the correct execution of the program because ~ need an integer operand. 它是正确执行程序所必需的,因为~需要一个整数操作数。

§ 6.5 Expressions §6.5表达式

  1. Some operators (the unary operator ~ , and the binary operators << , >> , & , ^ , and | , collectively described as bitwise operators) are required to have operands that have integer type . 一些运算符(一元运算符~以及二元运算符<<>>&^| ,统称为按位运算符) 需要具有整数类型的操作数 These operators yield values that depend on the internal representations of integers, and have implementation-defined and undefined aspects for signed types. 这些运算符产生的值取决于整数的内部表示,并且具有已签名类型的实现定义和未定义方面。

Compilers are smart-enough to analyze expressions, checks semantics of expressions, perform type checking and arithmetic conversions if required. 编译器足够智能 - 足以分析表达式,检查表达式的语义,执行类型检查和算术转换(如果需要)。 That's the reason that to apply ~ on char type we don't need to explicitly write ~(int)c — called explicit type casting (and do avoid errors). 这就是在char类型上应用~的原因,我们不需要显式写入~(int)c - 称为显式类型转换(并避免错误)。

Note: 注意:

  1. Value of c is promoted to int in expression ~c , but type of c is still unsigned char - its type does not. c值在表达式~c被提升为int ,但c类型仍然是unsigned char - 它的类型不是。 Don't be confused. 不要混淆。

  2. Important: result of ~ operation is of int type!, check below code (I don't have vs-compiler, I am using gcc): 重要: ~操作的结果是int类型!,检查下面的代码(我没有vs-compiler,我使用的是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; } 

    compile it, and run: 编译它,然后运行:

     $ gcc -std=gnu99 -Wall -pedantic xc -ox $ ./x sizeof(int) = 4, sizeof(unsigned char) = 1 sizeof(~c) = 4 

    Notice : size of result of ~c is same as of int , but not equals to unsigned char — result of ~ operator in this expression is int ! 注意~c的结果大小与int相同,但不等于unsigned char - 此表达式中~运算符的结果为int that as mentioned 6.5.3.3 Unary arithmetic operators 如上所述6.5.3.3一元算术运算符

    1. The result of the unary - operator is the negative of its (promoted) operand. 一元的结果-运算符是负的(推动)操作的。 The integer promotions are performed on the operand, and the result has the promoted type. 整数提升在操作数上执行, 结果具有提升类型。

Now, as @haccks also explained in his answer -that result of ~c on 32-bit machine and for value of c = 4 is: 现在,正如@haccks在他的回答中解释的那样 - 在32位机器上的~cc = 4值的结果是:

1111 1111 1111 1111 1111 1111 1111 1011

in decimal it is -5 — that is the output of your second code ! 在十进制中它是-5 - 这是你的第二个代码的输出!

In your first code , one more line is interesting to understand b = ~c; 在你的第一个代码中 ,另外一行很有意思理解b = ~c; , because b is an unsigned char variable and result of ~c is of int type, so to accommodate value of result of ~c to b result value (~c) is truncated to fit into the unsigned char type as follows: ,因为b是一个unsigned char变量和的结果~cint类型,所以,以适应的结果的值~cb的结果值(〜c)中被截断以适合成unsigned char类型如下:

    1111 1111 1111 1111 1111 1111 1111 1011  // -5 & 0xFF
 &  0000 0000 0000 0000 0000 0000 1111 1111  // - one byte      
    -------------------------------------------          
                                  1111 1011  

Decimal equivalent of 1111 1011 is 251 . 十进制等值1111 1011251 You could get same effect using: 使用以下方法可以获得相同的效

printf("\n ~c = %d", ~c  & 0xFF); 

or as suggested by @ouah in his answer using explicitly casting. 或者@ouah在他的回答中使用显式强制转换的建议。

When applying the ~ operator to c it gets promoted to int , the result is an int as well. ~运算符应用于c它会被提升为int ,结果也是一个int

Then 然后

  • in the 1st example the result gets converted to unsigned char and then promoted to signed int and printed. 在第一个示例中,结果将转换为unsigned char ,然后提升为signed int并打印。
  • in the 2nd example the result gets printed as signed int . 在第二个例子中,结果打印为signed int

It gives the op -5. 它给出了op -5。 why it didn't work? 为什么它不起作用?

Instead of: 代替:

printf("%d",~c);

use: 使用:

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

to get the same result as in your first example. 获得与第一个示例中相同的结果。

~ operand undergoes integer promotion and default argument promotion are applied to argument of variadic functions. ~操作数经过整数提升,默认参数提升应用于可变函数的参数。

Integer promotion, from the standard: 整数推广,来自标准:

If the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type. 如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数应转换为带有符号整数类型的操作数的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么 * 和 % 在这条规则中表现不同? - Why does * and % behave differently in this rule? 为什么 Boost Format 和 printf 在相同的格式字符串上表现不同 - Why do Boost Format and printf behave differently on same format string 为什么strptime()在OSX和Linux上表现不同? - Why does strptime() behave differently on OSX and on Linux? 为什么EOF在fgets和阅读中表现不同 - Why does EOF behave differently in fgets and read 为什么在使用struct时printf会表现出这种方式? - Why does printf behave this way when using struct? 为什么对于常数与变量,全宽度的移动表现不同? - Why does shifting by full width behave differently for constant versus variable? 为什么在这两种情况下gcc编译器的行为会有所不同? - Why does gcc compiler behave differently in these two cases? Printf function %p 在给定数组和指针时表现不同 - Printf function %p behave differently when it is given an array compared to a pointer C - 为什么 puts 行为异常,而 printf 在使用 scanf 时却没有? - C - Why does puts behave unexpectedly, but printf does not when using scanf? 为什么我在C中创建的这个“读取文件”功能在Linux中的行为与在Windows中的行为不同? - Why does this “read file” function I've created in C behave differently in Linux than it does in Windows?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM