Неявное преобразование C++ (подписано + неподписано)

Я понимаю, что в отношении неявных преобразований, если у нас есть операнд типа без знака и операнд типа со знаком, а тип операнда без знака совпадает (или больше) с типом операнда со знаком, знаковый операнд будет преобразован без подписи.

Так:

unsigned int u = 10;  
signed int s = -8;

std::cout << s + u << std::endl;

//prints 2 because it will convert `s` to `unsigned int`, now `s` has the value
//4294967288, then it will add `u` to it, which is an out-of-range value, so,
//in my machine, `4294967298 % 4294967296 = 2`

Что я не понимаю - я прочитал это, если подписанный операнд имеет больший тип, чем беззнаковый операнд:

  • если все значения в типе без знака соответствуют большему типу, то беззнаковый операнд преобразуется в тип со знаком

  • если значения в типе без знака не вписываются в больший тип, тогда операнд со знаком будет преобразован в тип без знака

так в следующем коде:

signed long long s = -8;
unsigned int u = 10;
std::cout << s + u << std::endl;

u будет преобразован в long long со знаком, потому что значения int могут помещаться в long long со знаком??

Если это так, то в каком сценарии меньшие значения типов не будут соответствовать большему?

3 ответа

Решение

Соответствующая цитата из Стандарта:

5 выражений [expr]

10 Многие бинарные операторы, которые ожидают операнды арифметического или перечислимого типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата. Эта модель называется обычными арифметическими преобразованиями, которые определяются следующим образом:

[2 пункта о равных типах или типах знака равенства опущены]

- В противном случае, если операнд с целочисленным типом без знака имеет ранг больше или равен рангу типа другого операнда, операнд с целочисленным типом со знаком должен быть преобразован в тип операнда с целочисленным типом без знака.

- В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, операнд с целочисленным типом без знака должен быть преобразован в тип операнда с целочисленным типом со знаком.

- В противном случае оба операнда должны быть преобразованы в целочисленный тип без знака, соответствующий типу операнда с целочисленным типом со знаком.

Давайте рассмотрим следующие 3 примера для каждого из 3 приведенных выше пунктов в системе, гдеsizeof(int) < sizeof(long) == sizeof(long long) (легко адаптируется к другим случаям)

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;

signed long int s2 = -4;
unsigned int u2 = 2;

signed long long int s3 = -4;
unsigned long int u3 = 2;

int main()
{
    std::cout << (s1 + u1) << "\n"; // 4294967294
    std::cout << (s2 + u2) << "\n"; // -2 
    std::cout << (s3 + u3) << "\n"; // 18446744073709551614  
}

Живой пример с выходом.

Первый пункт: типы одинакового ранга, поэтому signed int операнд преобразуется в unsigned int, Это влечет за собой преобразование значения, которое (с использованием дополнения до двух) дает напечатанное значение.

Второе предложение: тип со знаком имеет более высокий ранг и (на этой платформе!) Может представлять все значения типа без знака, поэтому беззнаковый операнд преобразуется в тип со знаком, и вы получаете -2

Третье предложение: подписанный тип снова имеет более высокий ранг, но (на этой платформе!) Не может представлять все значения беззнакового типа, поэтому оба операнда преобразуются в unsigned long longи после преобразования значения в подписанном операнде вы получите напечатанное значение.

Обратите внимание, что когда беззнаковый операнд будет достаточно большим (например, 6 в этих примерах), тогда конечный результат даст 2 для всех 3 примеров из-за переполнения целого числа без знака.

(Добавлено) Обратите внимание, что вы получаете еще более неожиданные результаты при сравнении этих типов. Рассмотрим приведенный выше пример 1 с <:

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;
int main()
{
    std::cout << (s1 < u1 ? "s1 < u1" : "s1 !< u1") << "\n";  // "s1 !< u1"
    std::cout << (-4 < 2u ? "-4 < 2u" : "-4 !< 2u") << "\n";  // "-4 !< 2u"
}

поскольку 2u сделан unsigned явно u Суффикс применяются те же правила. И результат, вероятно, не тот, который вы ожидаете при сравнении -4 < 2 при написании на C++ -4 < 2u...

signed int не вписывается в unsigned long long, Так что у вас будет это преобразование:signed int -> unsigned long long,

Обратите внимание, что стандарт C++11 здесь не говорит о больших или меньших типах, он говорит о типах с более низким или более высоким рангом.

Рассмотрим случай long int а также unsigned int где оба 32-битные. long int имеет больший ранг, чем unsigned int, но с тех пор long int а также unsigned int оба 32-битные, long int не может представлять все значения unsigned int,

Поэтому мы переходим к последнему случаю (C++11: 5.6p9):

  • В противном случае оба операнда должны быть преобразованы в тип целого без знака, соответствующий типу операнда с целым типом со знаком.

Это означает, что оба long int и unsigned int будет преобразован в unsigned long int,

Другие вопросы по тегам