为什么通过printf,补语的行为不同?

时间:2020-12-12 04:17:14

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.

然后我决定直接打印~c的值,而不是用d作为变量来保持~c的值。

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

It gives the output -5.

输出是-5。

Why didn't it work?

它为什么不工作?

6 个解决方案

#1


55  

In this statement:

在此声明:

printf("%d",~c);

the c is converted to int1 type before ~ (bitwise complement) operator is applied. 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.

在应用~(位补)运算符之前,将c转换为int1类型。这是因为整数提升,它被调用到~的操作数。在这种情况下,无符号字符类型的对象被提升为(有符号的)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 (e.g. -5)
  • ~c rvalue被评估为(带符号的)int值(如-5)
  • d=~c makes an implicit conversion from int to unsigned char, as d has such type. You may think of it as the same as d = (unsigned char) ~c. Notice that d cannot be negative (this is general rule for all unsigned types).
  • d=~c从int到无符号字符进行隐式转换,因为d有这种类型。你可以把它看成是d =(无符号字符)~c。注意,d不能为负数(这是所有未签名类型的通用规则)。
  • printf("%d", d); invokes default argument promotions, thus d is converted to int and the (nonnegative) value is preserved (i.e. the int type can represent all values of unsigned char type).
  • printf(" % d,d);调用默认参数升级,因此d被转换为int,(非负的)值被保留(即int类型可以表示无符号字符类型的所有值)。

1) assuming that int can represent all values of the unsigned char (see T.C.'s comment below), but it is very likely to happen in this way. More specifically, we assume that INT_MAX >= UCHAR_MAX holds. Typically the sizeof(int) > sizeof(unsigned char) holds and byte consist of eight bits. 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).

1)假设int可以表示无符号字符的所有值(见T.C.)。但它很可能以这种方式发生。更具体地说,我们假设INT_MAX >= UCHAR_MAX持有。通常,sizeof(int) > sizeof(unsigned char)包含,字节由8位组成。否则c将被转换成无符号整型(如由C11分单元§6.3.1.1 / p2),和格式说明符应该也相应改变了% u为了避免乌兰巴托(C11§7.21.6.1 / p9)。

#2


27  

char is promoted to int in printf statement before the operation ~ in second snippet. So c, which is

在第2段的操作之前,char被提升到printf语句中的int。所以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)

和位操作补充等于2的补的价值- 1(x ~ x =−−1)

1111 1111 1111 1111 1111 1111 1111 1011  

which is -5 in decimal in 2's complement form.

这是-5,以2的补码形式。

Note that the default promotion of char c to int is also performed in

注意,对char c的默认升级也会执行。

d = ~c;

before complement operation but the result is converted back to unsigned char as d is of type unsigned char.

在补码操作之前,但是结果被转换回无符号字符,因为d是无符号字符类型。

C11: 6.5.16.1 Simple assignment (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):

The type of an assignment expression is the type the left operand would have after lvalue conversion.

赋值表达式的类型是左操作数在lvalue转换后的类型。

#3


17  

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:

要理解代码的行为,您需要学习N1570委员会草案中提到的“整数提升”概念(在您的代码中,在不对无符号字符操作数进行操作之前,会隐式地发生这种情况):

§ 6.5.3.3 Unary arithmetic operators

  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".
  2. ~运算符的结果是其(提升的)操作数的位补(即,当且仅当转换操作数中的对应位未被设置时,结果中的每一位都被设置)。整数升序在操作数上执行,结果具有升序类型。如果提升类型是“无符号类型”,则表达式~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 ~). It is required for the correct execution of the program because ~ need an integer operand.

由于无符号字符类型小于(因为它需要更少的字节)int类型,-抽象机器(编译器)执行的隐式类型提升和变量c的值在编译时(在应用补码操作~之前)被提升为int。程序的正确执行需要它,因为~需要一个整数操作数。

§ 6.5 Expressions

  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.
  2. 一些运营商(一元运算符~,和二元操作符< <、> > &,^,|,集体称为逐位运算符)是需要整数类型的操作数。这些操作符产生的值依赖于整数的内部表示,并且对于签名类型具有实现定义的和未定义的方面。

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. Don't be confused.

    在表达式~c中,c的值被提升为int,但是c的类型仍然是无符号字符——它的类型不是。不要混淆。

  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 x.c -o x
    $ ./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! that as mentioned 6.5.3.3 Unary arithmetic operators

    注意:~c的结果大小与int相同,但不等于无符号字符- ~运算符的结果为int!如前所述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.
    2. 一元运算符的结果是其(提升)操作数的负值。整数升序在操作数上执行,结果具有升序类型。

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位机器上的~c和c = 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;, 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 = ~ c;,因为b是一个无符号字符变量和结果的~ c是int类型,所以适应值的结果~ c b的结果值(~ 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. You could get same effect using:

十进制的1111011等于251。你可以使用:

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

or as suggested by @ouah in his answer using explicitly casting.

或者如@ouah在他的答案中使用显式铸造。

#4


12  

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.
  • 在第一个示例中,结果被转换为无符号字符,然后升级为有符号int并打印。
  • in the 2nd example the result gets printed as signed int.
  • 在第二个示例中,结果被打印为符号int。

#5


10  

It gives the op -5. why it didn't work?

得到op -5。为什么它不工作?

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.

将~操作数进行整数推广,并将默认参数推广应用于变量函数的参数。

#6


8  

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.

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

#1


55  

In this statement:

在此声明:

printf("%d",~c);

the c is converted to int1 type before ~ (bitwise complement) operator is applied. 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.

在应用~(位补)运算符之前,将c转换为int1类型。这是因为整数提升,它被调用到~的操作数。在这种情况下,无符号字符类型的对象被提升为(有符号的)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 (e.g. -5)
  • ~c rvalue被评估为(带符号的)int值(如-5)
  • d=~c makes an implicit conversion from int to unsigned char, as d has such type. You may think of it as the same as d = (unsigned char) ~c. Notice that d cannot be negative (this is general rule for all unsigned types).
  • d=~c从int到无符号字符进行隐式转换,因为d有这种类型。你可以把它看成是d =(无符号字符)~c。注意,d不能为负数(这是所有未签名类型的通用规则)。
  • printf("%d", d); invokes default argument promotions, thus d is converted to int and the (nonnegative) value is preserved (i.e. the int type can represent all values of unsigned char type).
  • printf(" % d,d);调用默认参数升级,因此d被转换为int,(非负的)值被保留(即int类型可以表示无符号字符类型的所有值)。

1) assuming that int can represent all values of the unsigned char (see T.C.'s comment below), but it is very likely to happen in this way. More specifically, we assume that INT_MAX >= UCHAR_MAX holds. Typically the sizeof(int) > sizeof(unsigned char) holds and byte consist of eight bits. 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).

1)假设int可以表示无符号字符的所有值(见T.C.)。但它很可能以这种方式发生。更具体地说,我们假设INT_MAX >= UCHAR_MAX持有。通常,sizeof(int) > sizeof(unsigned char)包含,字节由8位组成。否则c将被转换成无符号整型(如由C11分单元§6.3.1.1 / p2),和格式说明符应该也相应改变了% u为了避免乌兰巴托(C11§7.21.6.1 / p9)。

#2


27  

char is promoted to int in printf statement before the operation ~ in second snippet. So c, which is

在第2段的操作之前,char被提升到printf语句中的int。所以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)

和位操作补充等于2的补的价值- 1(x ~ x =−−1)

1111 1111 1111 1111 1111 1111 1111 1011  

which is -5 in decimal in 2's complement form.

这是-5,以2的补码形式。

Note that the default promotion of char c to int is also performed in

注意,对char c的默认升级也会执行。

d = ~c;

before complement operation but the result is converted back to unsigned char as d is of type unsigned char.

在补码操作之前,但是结果被转换回无符号字符,因为d是无符号字符类型。

C11: 6.5.16.1 Simple assignment (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):

The type of an assignment expression is the type the left operand would have after lvalue conversion.

赋值表达式的类型是左操作数在lvalue转换后的类型。

#3


17  

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:

要理解代码的行为,您需要学习N1570委员会草案中提到的“整数提升”概念(在您的代码中,在不对无符号字符操作数进行操作之前,会隐式地发生这种情况):

§ 6.5.3.3 Unary arithmetic operators

  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".
  2. ~运算符的结果是其(提升的)操作数的位补(即,当且仅当转换操作数中的对应位未被设置时,结果中的每一位都被设置)。整数升序在操作数上执行,结果具有升序类型。如果提升类型是“无符号类型”,则表达式~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 ~). It is required for the correct execution of the program because ~ need an integer operand.

由于无符号字符类型小于(因为它需要更少的字节)int类型,-抽象机器(编译器)执行的隐式类型提升和变量c的值在编译时(在应用补码操作~之前)被提升为int。程序的正确执行需要它,因为~需要一个整数操作数。

§ 6.5 Expressions

  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.
  2. 一些运营商(一元运算符~,和二元操作符< <、> > &,^,|,集体称为逐位运算符)是需要整数类型的操作数。这些操作符产生的值依赖于整数的内部表示,并且对于签名类型具有实现定义的和未定义的方面。

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. Don't be confused.

    在表达式~c中,c的值被提升为int,但是c的类型仍然是无符号字符——它的类型不是。不要混淆。

  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 x.c -o x
    $ ./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! that as mentioned 6.5.3.3 Unary arithmetic operators

    注意:~c的结果大小与int相同,但不等于无符号字符- ~运算符的结果为int!如前所述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.
    2. 一元运算符的结果是其(提升)操作数的负值。整数升序在操作数上执行,结果具有升序类型。

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位机器上的~c和c = 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;, 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 = ~ c;,因为b是一个无符号字符变量和结果的~ c是int类型,所以适应值的结果~ c b的结果值(~ 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. You could get same effect using:

十进制的1111011等于251。你可以使用:

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

or as suggested by @ouah in his answer using explicitly casting.

或者如@ouah在他的答案中使用显式铸造。

#4


12  

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.
  • 在第一个示例中,结果被转换为无符号字符,然后升级为有符号int并打印。
  • in the 2nd example the result gets printed as signed int.
  • 在第二个示例中,结果被打印为符号int。

#5


10  

It gives the op -5. why it didn't work?

得到op -5。为什么它不工作?

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.

将~操作数进行整数推广,并将默认参数推广应用于变量函数的参数。

#6


8  

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.

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