Что происходит с побитовыми операторами и целочисленным продвижением?

У меня есть простая программа. Обратите внимание, что я использую целое число без знака с фиксированной шириной 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-байтовое целое число со знаком.

Похоже что побитовый не оператор (~) выполняет целочисленное продвижение со знаком, как это делают арифметические операторы. Тем не менее, левый оператор смещения (<<), кажется, способствует беззнаковый интеграл.

Я чувствую себя обязанным знать, когда компилятор что-то меняет за моей спиной. Если в моем анализе я прав, все ли побитовые операторы переводятся в 4-байтовое целое число? И почему некоторые подписаны, а некоторые без подписи? Я весьма озадачен!

Редактировать: Мое предположение о том, чтобы всегда получать положительные или всегда отрицательные значения, было неверным. Но из-за неправильного ответа я понимаю, что на самом деле происходит, благодаря замечательным ответам ниже.

10

Решение

[Expr.unary.op]

Операнд ~ должен иметь целочисленный или незаданный тип перечисления;
результат — дополнение его операнда. Интегральные акции
выполнила.

[Expr.shift]

Операторы сдвига << а также >> группа слева направо. […] Операнды должны быть целочисленного или незаданного типа перечисления и выполняются интегральные рекламные акции.

Что является неотъемлемым продвижением uint8_t (который обычно будет unsigned_char за кулисами)?

[Conv.prom]

Значение типа integer, отличного от bool, char16_t, char32_t, или же
wchar_t чей ранг целочисленного преобразования (4.13) меньше, чем ранг
int может быть преобразовано в тип значения int если int может представлять все
значения типа источника; в противном случае исходное значение может быть
преобразуется в значение типа unsigned int,

Так intпотому что все значения uint8_t может быть представлен int,

Что такое int(12) << 1 ? int(24),

Что такое ~int(12) ? int(-13),

8

Другие решения

Из соображений производительности язык C и 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 в строках формата и %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
5

Заглянуть в дополнение двух и как компьютер хранит отрицательные целые числа.
Попробуй это

#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, он будет отрицательным

** РЕДАКТИРОВАТЬ **
Ну, я могу придумать причину, по которой операторы shift будут использовать unsigned int. Рассмотрим операцию правого сдвига >> если вы сдвинете вправо -12, вы получите 122 вместо -6. Это потому, что он добавляет ноль в начале без учета знака

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 (не unsigned int). Продвижение в int имеет приоритет над продвижением unsigned,

3
По вопросам рекламы [email protected]