Возможно, стоит объяснить, что произошло, чтобы получить число 18446744073692774400. С технической точки зрения, написанные вами выражения вызывают «неопределенное поведение», и поэтому компилятор мог получить в результате что угодно; однако, предполагая, что int
является 32-битным типом, что в настоящее время почти всегда так, вы получите тот же «неправильный» ответ, если напишете
uint64_t x = (int) (255u*256u*256u*256u);
и это выражение не вызывает неопределенное поведение. (Преобразование из unsigned int
в int
связано с поведением, определяемым реализацией, но поскольку за многие годы никто не создал ЦП с дополнением до единицы или процессором со знаком и величиной, все реализации, с которыми вы, вероятно, столкнетесь, точно определяют его. точно так же.) Я написал приведение в стиле C, потому что все, что я здесь говорю, в равной степени относится к C и C++.
Во-первых, давайте посмотрим на умножение. Я пишу правую часть в шестнадцатеричном формате, потому что так легче увидеть, что происходит.
255u * 256u = 0x0000FF00u
255u * 256u * 256u = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)
Этот последний результат, 0xFF000000u
, имеет старший бит набора 32-битных чисел. Следовательно, приведение этого значения к 32-битному типу signed приводит к тому, что оно становится отрицательным, как если бы из него было вычтено 232 (это операция, определяемая реализацией, о которой я упоминал выше). ).
(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216
Я пишу здесь шестнадцатеричное число без суффикса u
, чтобы подчеркнуть, что битовый шаблон значения не меняется при преобразовании его в тип со знаком; это только переосмыслено.
Теперь, когда вы присваиваете -16777216 переменной uint64_t
, она преобразуется обратно в беззнаковую как если бы путем добавления 264. (В отличие от преобразования без знака в знак, эта семантика предписана стандартом.) Это действительно изменяет битовый шаблон, устанавливая все старшие 32 бита числа в 1 вместо 0, когда вы ожидал:
(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
А если записать 0xFFFFFFFFFF000000
в десятичном виде, получится 18446744073692774400.
В качестве заключительного совета: всякий раз, когда вы получаете «невозможное» целое число из C или C++, попробуйте распечатать его в шестнадцатеричном формате; таким образом гораздо легче увидеть странности арифметики фиксированной ширины с дополнением до двух.
person
zwol
schedule
20.09.2012
g++ -Wall -Wextra -pedantic
, компилятор довольно четко говорит мне: предупреждение: целочисленное переполнение в выражении [-Woverflow]. Microsoft Visual Studio также диагностирует эту проблему: предупреждение C4307: '*': целочисленное переполнение константы. Я надеюсь, что это послужит уроком для компиляции с предупреждениями в будущем. - person R. Martinho Fernandes   schedule 20.09.2012255ULL * 256 * 256 * 256
может работать. - person aschepler   schedule 20.09.2012unsigned long long
гарантированно имеет не менее 64 бит? (Правда, было бы глупо, если бы компилятор поддерживал тип__int64
и не делал его псевдонимом дляlong long
.) - person aschepler   schedule 20.09.2012unsigned long long
отсутствует в C++03, поэтому для его использования вам потребуется либо специфичное для компилятора расширение для C++03, либо (на данный момент) специфичная для компилятора частичная реализация C++11. На практике все имеютlong long
. - person Steve Jessop   schedule 20.09.2012ULLONG_MAX
равно18446744073709551615
, что соответствует стандарту не менее 64 бит. - person Steve Jessop   schedule 20.09.2012