Переполнение появляется даже при использовании unsigned long int
Когда я делаю следующий расчет:
unsigned long long int data_size = 60123456 * 128 * sizeof(double);
printf("data_size= %llu \n", data_size);
Я неожиданно получаю предупреждение о переполнении:
test.c:20:49: warning: overflow in expression; result is -894132224 with type 'int' [-Winteger-overflow]
unsigned long long int data_size = 60123456 * 128 * sizeof(double);
^
1 warning generated.
Я не могу понять, почему эта ошибка появляется, хотя я использую unsigned long long int
! Может кто-нибудь объяснить, почему? Спасибо вам
Ответы
Ответ 1
Переполнение происходит до того, как значение присваивается переменной. Чтобы избежать использования длинного длинного суффикса:
60123456LL * 128 * sizeof(double)
(комментарии также предлагают ULL, который здесь не нужен, потому что значения находятся в пределах диапазона подписки, но это будет общий путь)
Ответ 2
Вы слишком поздно конвертируете в "long long". 60123456 * 128
все еще является переполняющим int-выражением.
Попробуйте 60123456LL * 128
. (LL, потому что результирующее значение не гарантируется в соответствии с длинным.)
Ответ 3
unsigned long long int data_size = 60123456 * 128 * sizeof(double); // trouble-some code
Тип адресата unsigned long long int data_size =
не имеет отношения к расчету продукта 60123456 * 128 * sizeof(double)
.
Лучше всего застраховать математику, используя, по крайней мере, размер целевого типа, чтобы избежать переполнения. В случае OP это означает константу с LLU
.
Существует 2 вычисления продукта, каждый со своей собственной математикой.
60123456
- это int
или long
в зависимости от диапазона int
. Предположим, что это int
.
60123456 * 128
является int * int
. Математический продукт 7695802368
превосходит 32-разрядный целочисленный целочисленный диапазон, таким образом со знаком целочисленного переполнения или undefined поведение (UB) с 32-разрядным int
.
Если 60123456 * 128
не переполнялся, говорит 64-бит int
, то следующее умножение будет * sizeof(double);
, и поэтому int * size_t
приводит к типу продукта size_t
.
Вычисление продукта должно использовать как минимум unsigned long long
математику, как показано ниже:
unsigned long long int data_size = 1LLU * 60123456 * 128 * sizeof(double);
// or
unsigned long long int data_size = 60123456LLU * 128 * sizeof(double);
// or
unsigned long long int data_size = 60123456;
data_size *= 128 * sizeof(double);
// or avoiding naked magic numbers
#define A_HEIGHT 60123456
#define A_WIDTH 128
unsigned long long int data_size = 1LLU * A_HEIGHT * A_WIDTH * sizeof(double);
sizeof (double)
указывает, что код пытается найти размер некоторой 2D-подобной структуры. Я бы предположил, что код выглядит следующим образом. Обратите внимание, что тип результата sizeof
равен size_t
, поэтому математика продукта выполняется с использованием не менее size_t
math.
size_t data_size = sizeof(double) * 60123456 * 128;
printf("data_size= %zu\n", data_size);
См. также Зачем писать 1000 000 000 как 1000 * 1000 * 1000 в C? и мой ответ причины не использовать 1000 * 1000 * 1000 для соответствующих сведений..
Ответ 4
Константы 60123456
и 128
имеют тип int
, поэтому выражение 60123456 * 128
также имеет тип int
. Значение этого выражения переполнило бы диапазон int
. Компилятор может обнаружить его в этом случае, потому что оба операнда являются константами.
Вы должны использовать суффикс ULL
в первом операнде, чтобы сделать тип константы unsigned long long
. Таким образом, он соответствует назначению типа. Затем для любой операции, связанной с этим значением, другой операнд будет продвигаться до unsigned long long
до того, как будет применена операция, и у вас не будет переполнения.
Итак, получившееся выражение должно выглядеть так:
unsigned long long int data_size = 60123456ULL * 128 * sizeof(double);
Ответ 5
Лексер присоединяет типы к константам во время фазы перевода 7 (преобразование токенов предварительной обработки в токены C). cf ISO9899 5.1.1.2 Translation phases
. Как говорили другие, лексер будет прикреплять к вашим константам тип int
вместо unsigned long long
, как вам угодно, и для того, чтобы default arithmetic conversions
сгенерировал константу типа unsigned long long
в правой части задания вам нужно сказать лексеру, чтобы прикрепить тип unsigned long long
по крайней мере к одной константе из операции *
, которая генерирует переполнение, поэтому записывая 60123456ULL * 128
или 60123456 * 128ULL
или и то, и другое.