Рассмотрим следующий код:
#include <iostream>
using namespace std;
int main() {
// the following is expected to not print 4000000000
// because the result of an expression with two `int`
// returns another `int` and the actual result
// doesn't fit into an `int`
cout << 2 * 2000000000 << endl; // prints -294967296
// as such the following produces the correct result
cout << 2 * 2000000000U << endl; // prints 4000000000
}
Я немного поиграл с приведением результата к различным целочисленным типам и натолкнулся на странное поведение.
#include <iostream>
using namespace std;
int main() {
// unexpectedly this does print the correct result
cout << (unsigned int)(2 * 2000000000) << endl; // prints 4000000000
// this produces the same wrong result as the original statement
cout << (long long)(2 * 2000000000) << endl; // prints -294967296
}
Я ожидал, что оба из следующих утверждений не дадут правильного результата, почему один из них преуспел, а другой нет?
Слишком много путаницы происходит у людей, пытающихся ответить на этот вопрос.
Давайте рассмотрим:
2 * 2000000000
Это int
умножается на int
, § 5/4 говорит нам:
Если во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение не определено.
Этот результат определен математически, но находится ли он в диапазоне представимых значений для int
?
Это зависит от. На многих распространенных архитектурах int
имеет 32 бита для представления значений, что дает ему максимальное значение 2 147 483 647. Поскольку математический результат этого равен 4 000 000 000, такая архитектура не сможет представлять значение, и поведение не определено. (Это в значительной степени убивает вопрос, потому что теперь поведение всей программы не определено.)
Но это зависит только от платформы. Если int
вместо этого был шириной 64 бита (примечание: long long
гарантированно имеет как минимум 64 бита для представления значений), результат будет соответствовать просто отлично.
Давайте просто исправим проблему и перейдем прямо к этому:
int x = -294967296; // -294,967,296
И давайте далее скажем, что это вписывается в диапазон int
(который для 32 бит int
оно делает).
Теперь давайте приведем это к unsigned int
:
unsigned int y = static_cast<unsigned int>(x);
Какова стоимость y
? Это не имеет ничего общего с битовым представлением x
.
Не существует «приведения битов», когда компилятор просто обрабатывает биты как количество без знака. Конверсии работают с ценности. значение из signed int
преобразован в unsigned int
определено в §4.7 / 2:
Если тип назначения является беззнаковым, полученное значение является наименьшим целым числом без знака, соответствующим исходному целому числу (по модулю 2N где n — количество битов, используемых для представления типа без знака). [Примечание: в представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). —Конечная записка]
Для нас на нашем 32-битном (unsigned
) int
система, это означает 4000000000. Это работает независимо от битов: комплимент двоим, комплимент одному, магический комплимент и т. д. Это не имеет значения.
причина вы видите значение, которое вы хотели в первой части (игнорируя UB), это то, что на компьютере с комплиментами двоих различие между знаковыми и беззнаковыми целыми числами действительно заключается в различном просмотре битов. Итак, когда вы умножили эти два int
вы действительно умножали два целых числа без знака, игнорировали переполнение и рассматривали результат как целое число со знаком. Затем актерский состав снова меняет ваш взгляд.
Но кастинг работает независимо от битов!
В int значение 4,000,000,000
написано как 1110 1110 0110 1011 0010 1000 0000 0000
В неподписанном int значение 4,000,000,000
написано как 1110 1110 0110 1011 0010 1000 0000 0000
Глядя на эти два, вы можете видеть, что они одинаковы.
Разница заключается в том, как биты читаются в int
а также unsigned int
, в обычном int
самый важный бит используется, чтобы сказать, является ли число отрицательным или нет.
В C ++ тип выражения не зависит от среды кода (обычно).
Поэтому подвыражение 2 * 2000000000 имеет одинаковый тип и значение в одной и той же системе, независимо от контекста содержащего выражения, оно int
(так как оба операнда оператора * int
с). И это будет 4000000000, но в вашей архитектуре оно изменилось и изменилось на -294967296 из-за переполнения.
Приведение к long long
не будет менять значение, потому что long long
может представлять -294967296 просто отлично.
На самом деле это гораздо интереснее, что cout << (unsigned int)(2 * 2000000000) << endl;
работает. Как unsinged int
не может содержать -294967296, переполнение происходит снова. -294967296 и 4000000000 являются конгруэнтными по модулю 2 ^ 32, так что это будет новое значение. (Обновлено с лучшего ответа GManNickG).
Чтобы проиллюстрировать более глубокую проблему, вы можете попробовать
cout << (unsigned int)(2 * 2000000000 / 2) << endl;
Деление будет выполнено -294967296, а двоичное представление -147483648 будет преобразовано в беззнаковое, которое равно 4147483648.
В третьем (странном) случае работающая программа делает это:
2 * 2000000000 = binary number (11101110011010110010100000000000)
print it as unsigned = 4000000000
(interprets the first bit (1) as part of the unsigned number)
Четвертый случай:
2 * 2000000000 = binary number (11101110011010110010100000000000, same as above)
print it as signed = -294967296
(interprets the first bit (1) as negative number)
Важно понять, что выражение 2 * 2000000000 приводит к последовательности байтов, а затем интерпретируется как говорит операция приведения.
Обратите внимание, что целочисленное переполнение со знаком является неопределенным поведением. В заключение, все может случиться. В том числе и невинно правильные результаты.
Оба целочисленных литерала 2
а также 2000000000
шириной 32 бита. Результат будет переполнен, как говорит ваш компилятор:
warning: integer overflow in expression [-Woverflow]
Результатом умножения остается 32-разрядное целое число со знаком. И, в этом случае, результат переполнения, к счастью, является правильным, если рассматривать его как 32-разрядное целое число без знака. Вы можете наблюдать это при приведении битового шаблона к 32-битному unsigned int
,
Однако если вы приведете значение к целочисленному типу большей ширины (например, 64 бита), начальные байты будут дополнены ff
(расширение знака) и, таким образом, дает ложные результаты.
#include <iostream>
int main() {
long long x = 2 * 2000000000; // 8 byte width
unsigned int y = 2 * 2000000000; // 4 byte width
unsigned long z = 2 * 2000000000; // 8 byte width
std::cout << std::hex << x << " " << std::dec << x << std::endl;
// output is: ffffffffee6b2800 -294967296
std::cout << std::hex << y << " " << std::dec << y << std::endl;
// output is: ee6b2800 4000000000
std::cout << std::hex << z << " " << std::dec << z << std::endl;
// output is: ffffffffee6b2800 18446744073414584320
}