按位运算符和整数提升是怎么回事?

What is going on with bitwise operators and integer promotion?

我有一个简单的程序。 请注意,我使用了一个大小为 1 字节的无符号固定宽度整数。

#include <cstdint>
#include <iostream>
#include <limits>

int main()
{
    uint8_t x = 12;
    std::cout << (x << 1) << '\n';
    std::cout << ~x;

    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();

    return 0;
}

我的输出如下。

24
-13

我测试了更大的数字,运算符 << 总是给我正数,而运算符 ~ 总是给我负数。然后我使用 sizeof() 并发现...

When I use the left shift bitwise operator(<<), I receive an unsigned 4 byte integer.

When I use the bitwise not operator(~), I receive a signed 4 byte integer.

按位非运算符 (~) 似乎像算术运算符一样进行有符号整数提升。但是,左移运算符(<<)似乎提升为无符号积分。

我觉得有义务知道编译器何时在我背后更改某些内容。如果我的分析是正确的,那么所有按位运算符是否都提升为 4 字节整数?为什么有的有签名有的没有签名?我很困惑!

编辑: 我总是得到正值或总是得到负值的假设是错误的。但是从错误中,由于下面的伟大答案,我明白了真正发生的事情。

研究 two's complement 以及计算机如何存储负整数。
试试这个

#include <cstdint>
#include <iostream>
#include <limits>
int main()
{
uint8_t x = 1;
int shiftby=0;
shiftby=8*sizeof(int)-1;
std::cout << (x << shiftby) << '\n'; // or std::cout << (x << 31) << '\n';

std::cout << ~x;

std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
return 0;
}

输出为-2147483648

一般来说,如果有符号数的第一位是 1,则被认为是负数。当你拿一个大数字并转移它时。如果你移动它使得第一位是 1 它将是负数

** 编辑 **
好吧,我能想到移位运算符使用 unsigned int 的原因。考虑右移操作 >> 如果你右移 -12 你会得到 122 而不是 -6。这是因为它在开头添加了一个零而没有考虑符号

[expr.unary.op]

The operand of ~ shall have integral or unscoped enumeration type; the result is the one’s complement of its operand. Integral promotions are performed.

[expr.shift]

The shift operators << and >> group left-to-right. [...] The operands shall be of integral or unscoped enumeration type and integral promotions are performed.

uint8_t的积分推广是什么(一般都是unsigned_char在幕后)?

[conv.prom]

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

所以int,因为一个uint8_t的所有值都可以用int表示。

什么是int(12) << 1int(24).

什么是~int(12)int(-13).

I tested larger numbers and operator << always gives me positive numbers, while operator ~ always gives me negative numbers. I then used sizeof() and found...

错了,测试一下:

uint8_t v = 1;
for (int i=0; i<32; i++) cout << (v<<i) << endl;

给出:

1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
-2147483648

uint8_t是一个8位长无符号整数类型,可以表示[0,255]范围内的值,因为该范围包含在int的范围内,所以提升为int(不是 unsigned int)。升级到 int 优先于升级到 unsigned

出于性能原因,C 和 C++ 语言将 int 视为 "most natural" 整数类型,而不是 "smaller" 而不是 int 的类型被认为是某种类型"storage"类型。

当您在表达式中使用存储类型时,它会自动转换为 int 或隐式转换为 unsigned int。例如:

// Assume a char is 8 bit
unsigned char x = 255;
unsigned char one = 1;

int y = x + one; // result will be 256 (too large for a byte!)
++x;             // x is now 0

发生的事情是第一个表达式中的 xone 已隐式转换为整数,已计算加法并将结果存储回整数。换句话说,计算尚未使用两个无符号字符执行。

同样,如果表达式中有一个 float 值,编译器要做的第一件事就是将其提升为 double(换句话说,float 是一种存储类型,并且double 是浮点数的自然大小)。这就是为什么如果你使用 printf 来打印浮点数你不需要说 %lf int 格式字符串并且 %f 就足够了(%lf 需要scanf 但是因为该函数 存储结果 float 可以小于 double).

C++ 使事情变得相当复杂,因为在将参数传递给函数时,您可以区分 int 和更小的类型。因此,在每个表达式中执行转换并不总是正确的......例如你可以有:

void foo(unsigned char x);
void foo(int x);

哪里

unsigned char x = 255, one = 1;
foo(x);       // Calls foo(unsigned char), no promotion
foo(x + one); // Calls foo(int), promotion of both x and one to int