Как работают правила продвижения, когда сигнатура с обеих сторон двоичного оператора отличается? [дубликат]

этот вопрос уже есть ответ здесь:

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

// 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. Однако в общем случае я не вижу, как это можно решить. Это неопределенное поведение, которое я вижу, или что-то еще?

3 ответов


это явно описано в §5/9:

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

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

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

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

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

потому что в этом случае значение us + neg не представляется int, стоимостью result определена реализация - §4.7 / 3:

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


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

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

основная идея правил сохранения ценности что он будет способствовать большему знаковому типу, если это может представлять все значения меньшего типа. Например, 16-разрядное unsigned short может быть повышен до 32-бит signed int, потому что каждое возможное значение unsigned short может быть представлен как signed int. Типы будут повышены до типа без знака, если и только если это необходимо для сохранения значений меньшего типа (например, если unsigned short и signed int оба 16 бит, потом 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, так что "что!"нога if будут приняты. С правилами сохранения значения, вы can удается получить некоторые странные результаты, немного похожие на это, но 1) в первую очередь на DOS-подобных системах, где int имеет тот же размер, что и short, и 2) это, как правило, сложнее сделать в любом случае.


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

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