Подтвердить что ты не робот

Как действуют правила продвижения, когда отличия по обе стороны от бинарного оператора различаются?

Рассмотрим следующие программы:

// http://ideone.com/4I0dT
#include <limits>
#include <iostream>

int main()
{
    int max = std::numeric_limits<int>::max();
    unsigned int one = 1;
    unsigned int result = max + one;
    std::cout << result;
}

и

// http://ideone.com/UBuFZ
#include <limits>
#include <iostream>

int main()
{
    unsigned int us = 42;
    int neg = -43;
    int result = us + neg;
    std::cout << result;
}

Как оператор + "знает", который является правильным типом для возврата? Общее правило состоит в том, чтобы преобразовать все аргументы в самый широкий тип, но здесь нет четкого "победителя" между int и unsigned int. В первом случае unsigned int должен быть выбран в результате operator+, потому что я получаю результат 2147483648. Во втором случае он должен выбрать int, потому что я получаю результат -1. Но я не вижу в общем случае, как это разрешимо. Это поведение undefined, которое я вижу или что-то еще?

4b9b3361

Ответ 1

Это явно указано в § 5/9:

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

  • Если любой из операндов имеет тип long double, другой должен быть преобразован в long double.
  • В противном случае, если любой операнд double, другой должен быть преобразован в double.
  • В противном случае, если любой операнд float, другой должен быть преобразован в float.
  • В противном случае интегральные рекламные акции должны выполняться в обоих операндах.
  • Затем, если любой операнд unsigned long, другой должен быть преобразован в unsigned long.
  • В противном случае, если один операнд является long int, а другой unsigned int, то если a long int может представлять все значения a unsigned int, unsigned int преобразуется в long int; в противном случае оба операнда должны быть преобразованы в unsigned long int.
  • В противном случае, если любой операнд long, другой должен быть преобразован в long.
  • В противном случае, если любой операнд unsigned, другой должен быть преобразован в unsigned.

[Примечание: в противном случае единственным оставшимся случаем является то, что оба операнда int]

В обоих ваших сценариях результат operator+ равен unsigned. Следовательно, второй сценарий эффективно:

int result = static_cast<int>(us + static_cast<unsigned>(neg));

Поскольку в этом случае значение us + neg не может быть представлено int, значение result определяется реализацией – §4.7/3:

Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.

Ответ 2

До того, как C был стандартизирован, между компиляторами были различия, некоторые из которых придерживались правил сохранения значений, а другие правила "сохраняли знак". Сохранение знака означало, что если любой операнд был неподписанным, результат был неподписанным. Это было просто, но время от времени давало довольно неожиданные результаты (особенно когда отрицательное число было преобразовано в unsigned).

C, стандартизованный на более сложных правилах сохранения значений. В соответствии с правилами сохранения значений продвижение может/зависит от реальных диапазонов типов, поэтому вы можете получать разные результаты для разных компиляторов. Например, на большинстве компиляторов MS-DOS int имеет тот же размер, что и short, а long отличается от. Во многих современных системах int имеет тот же размер, что и long, а short отличается от. При соблюдении правил сохранения значений это может привести к тому, что продвинутый тип будет отличаться между ними.

Основная идея правил сохранения значений заключается в том, что она будет способствовать более крупному подписанному типу, если это может представлять все значения меньшего типа. Например, 16-разрядный unsigned short можно повысить до 32-битного signed int, потому что все возможные значения unsigned short могут быть представлены как signed int. Типы будут продвигаться к неподписанному типу тогда и только тогда, когда необходимо сохранить значения меньшего типа (например, если unsigned short и signed int являются шестью битами, то a signed int не может представлять все возможные значения unsigned short, поэтому unsigned short будет повышаться до unsigned int).

Когда вы назначаете результат как есть, результат все равно будет преобразован в тип адресата, поэтому большая часть этого имеет относительно небольшую разницу - по крайней мере, в большинстве типичных случаев, где он просто скопирует бит в результат, и вам решать, следует ли интерпретировать это как подписанное или неподписанное.

Если вы не назначаете результат, например, при сравнении, все может стать довольно уродливым. Например:

unsigned int a = 5;
signed int b = -5;

if (a > b)
    printf("Of course");
else
    printf("What!");

В правилах сохранения знака b будет присвоено значение unsigned и в процессе станет равным UINT_MAX - 4, поэтому "What!" нога if. Со значениями правил сохранения вы можете получить некоторые странные результаты, также похожие на это, но 1) в первую очередь на DOS-подобные системы, где int имеет тот же размер, что и short, и 2) обычно сложнее сделайте это в любом случае.

Ответ 3

Выбор любого типа, в который вы вставляете результат или, по крайней мере, cout, выполняет этот тип во время вывода.

Я не помню точно, но я думаю, что компиляторы С++ генерируют один и тот же арифметический код для обоих, он только сравнивает и выводит информацию о знаке.