Как работают правила продвижения, когда сигнатура с обеих сторон двоичного оператора отличается? [дубликат]
этот вопрос уже есть ответ здесь:
рассмотрим следующие программы:
// 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
, theunsigned 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++ генерируют один и тот же арифметический код для обоих, это только сравнивает и выводит, что заботится о знаке.