Что происходит с побитовыми операторами и целым продвижением?
У меня простая программа. Обратите внимание, что я использую целое число без знака с фиксированной шириной 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()
и нашел...
Когда я использую побитовый оператор с левым сдвигом (<<
), я получаю четырехзначное целое число без знака.
Когда я использую побитовый оператор (~
), я получаю подписанное 4-байтовое целое.
Кажется, что побитовое не оператор (~
) выполняет подписанную интегральную продвижение, как это делают арифметические операторы. Однако левый оператор сдвига (<<
), по-видимому, способствует беззнаковому интегралу.
Я чувствую себя обязанным знать, когда компилятор что-то меняет за моей спиной. Если я правильно в своем анализе, все битовые операторы продвигают до 4 байтовых целых чисел? И почему некоторые подписываются и некоторые без знака? Я так смущен!
Изменить: Мое предположение о том, чтобы всегда получать положительные или всегда получать отрицательные значения, было неправильным. Но из-за того, что я ошибаюсь, я понимаю, что на самом деле происходит благодаря большим ответам ниже.
Ответы
Ответ 1
[expr.unary.op]
Операнд ~
должен иметь целочисленный или неперечисленный тип перечисления; результат является дополнением к его операнду. Интегральные рекламные акции выполняется.
[expr.shift]
Операторы сдвига <<
и >>
группируются слева направо. [...] Операнды должны быть целочисленного или не подлежащего регистрации типа перечисления и интегральных рекламных акций выполняются.
Какова интегральная поддержка uint8_t
(которая обычно будет unsigned_char
за кулисами)?
[conv.prom]
Значение целочисленного типа, отличного от bool
, char16_t
, char32_t
или wchar_t
, чей целочисленный ранг преобразования (4.13) меньше ранга int
может быть преобразован в prvalue типа int
, если int
может представлять все значения типа источника; в противном случае исходное значение может быть преобразуется в prvalue типа unsigned int
.
So int
, потому что все значения a uint8_t
могут быть представлены int
.
Что такое int(12) << 1
? int(24)
.
Что такое ~int(12)
? int(-13)
.
Ответ 2
По соображениям производительности язык C и С++ рассматривает int
как "самый естественный" целочисленный тип, а типы "меньше", чем int
, относятся к типу типа "хранения".
Когда вы используете тип хранилища в выражении, он автоматически преобразуется в 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
произошло то, что x
и one
в первом выражении были неявно преобразованы в целые числа, добавление было вычислено и результат был сохранен обратно в целое число. Другими словами, вычисление НЕ выполнялось с использованием двух символов без знака.
Аналогично, если в выражении есть значение float
, первое, что сделает компилятор, это продвигать его на double
(другими словами float
- это тип хранилища, а double
- это естественный размер для чисел с плавающей запятой). Именно по этой причине, если вы используете printf
для печати поплавков, вам не нужно указывать %lf
int строки формата и %f
достаточно (%lf
требуется для scanf
, однако, поскольку эта функция сохраняет результат, а float
может быть меньше double
).
С++ затруднил проблему совсем немного, потому что при передаче параметров в функции вы можете различать 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
Ответ 3
Я тестировал большие числа и оператор < всегда дает мне положительный числа, а оператор ~ всегда дает отрицательные числа. Затем я использовал sizeof() и нашел...
Неправильно, протестируйте его:
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
(не ). Продвижение int
имеет приоритет над продвижением до unsigned
.
Ответ 4
Посмотрите два дополнения и то, как компьютер хранит отрицательные целые числа.
Попробуйте это
#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. Это связано с тем, что он добавляет нуль в начале без учета знака