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

Why does the complement behave differently through printf?

我正在阅读有关按位运算符的一章,我遇到了 1 的补码运算符程序并决定 运行 它在 Visual C++ 上。

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

它给出了有效的输出:251

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

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

它给出了输出-5

为什么不起作用?

在此声明中:

printf("%d",~c);

c转换为int1类型before~(按位补码) 运算符被应用。这是因为 整数提升 ,它们被调用到 ~ 的操作数。在这种情况下,unsigned char 类型的对象被提升为(有符号)int,然后(在 ~ 运算符评估之后)由 printf 函数使用,匹配 %d 格式说明符。

请注意,默认参数提升(因为printf是一个可变参数函数)在这里没有任何作用,因为对象已经是类型int .

另一方面,在这段代码中:

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

发生以下步骤:

  • c 整数提升的对象 因为 ~(以同样的方式,如上所述)
  • ~c 右值被评估为(有符号)int 值(例如 -5
  • d=~c 进行从 intunsigned 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).

~ 运算符应用于 c 时,它会提升为 int,结果也是 int

然后

  • 在第一个示例中,结果被转换为 unsigned char,然后提升为 signed int 并打印。
  • 在第二个示例中,结果打印为 signed int

整数提升,来自标准:

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.

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  

这是 -5 十进制的 2 的补码形式。

请注意,char cint 的默认提升也在

中执行
d = ~c;

在补码运算之前,但结果被转换回 unsigned char,因为 d 的类型为 unsigned char

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.

6.5.16 (p3):

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

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

而不是:

printf("%d",~c);

使用:

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

获得与第一个示例相同的结果。

~ 操作数进行整数提升,默认参数提升应用于可变参数函数的参数。

要了解代码的行为,您需要学习称为 'Integer Promotions' 的概念(在对 unsigned char 操作数进行按位 NOT 操作之前隐式发生在代码中)如 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".

因为 unsigned char 类型比 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.

编译器足够智能,可以分析表达式、检查表达式的语义、执行类型检查和算术转换(如果需要)。这就是为什么要在 char 类型上应用 ~ 我们不需要显式编写 ~(int)c — 称为显式类型转换(并避免错误)。

注:

  1. c 的值在表达式 ~c 中提升为 int,但 c 的类型仍然是 unsigned char - 它类型没有。不要混淆。

  2. 重要: ~ 操作的结果是 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——[=的结果此表达式中的 16=] 运算符是 int!如前所述 6.5.3.3 Unary arithmetic operators

    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.

现在,正如@haccks 在他的 中所解释的那样 - 在 32 位机器上 ~c 的结果和 c = 4 的值是:

1111 1111 1111 1111 1111 1111 1111 1011

在十进制中是 -5 — 这是你的 第二个代码 !

的输出

在你的第一个代码中,多一行对理解b = ~c;很有趣,因为b是一个unsigned char变量和结果~cint 类型,所以为了容纳 ~c 的结果值到 b 结果值 (~c) 是 t运行适合 unsigned char 类型 如下:

    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 在他的 中建议使用显式转换。