Этот вопрос является результатом другого ТАК вопрос.
Пример кода
#include <iostream>
int main()
{
unsigned long b = 35000000;
int i = 100;
int j = 30000000;
unsigned long n = ( i * j ) / b; // #1
unsigned long m = ( 100 * 30000000 ) / b; // #2
std::cout << n << std::endl;
std::cout << m << std::endl;
}
Выход
85
85
Компиляция этого кода с g++ -std=c++11 -Wall -pedantic -O0 -Wextra
выдает следующее предупреждение:
9:28: warning: integer overflow in expression [-Woverflow]
Вопросы
Правильно ли я считаю, что #1
а также #2
вызвать неопределенное поведение, потому что промежуточный результат 100 * 30000000
не вписывается в int
? Или вывод, который я вижу, четко определен?
Почему я получаю только предупреждение с #2
?
Да, это неопределенное поведение, и результат, который вы получаете, обычно… unsigned long
это 64-битный тип.
U Это UB, поэтому нет никаких гарантий.
Да, это неопределенное поведение. Что делать, если вы просто остановились прямо там и return m
? Компилятор должен перейти из точки A в точку B, и вы сказали ему сделать это, выполнив этот расчет (что невозможно). Компилятор может решить оптимизировать этот оператор таким образом, чтобы избежать переполнения, но, насколько я знаю, стандарт не требует от оптимизатора что-либо делать.
Вы явно говорите gcc не оптимизировать вообще (-O0
), поэтому я предполагаю, что он не знает значений i
а также j
в таком случае. Обычно вы изучаете значения из-за постоянное складывание, но, как я уже сказал, вы сказали не оптимизировать.
Если вы перезапустите это, а оно все еще не упомянет, существует также вероятность того, что это предупреждение будет сгенерировано перед запуском оптимизатора, поэтому оно просто недостаточно умно, чтобы вообще делать постоянное свертывание для этого шага.
1) Да, это неопределенное поведение.
2) Поскольку # 1 включает в себя переменные (не константы), поэтому компилятор вообще не знает, будет ли он переполнен (хотя в этом случае это происходит, и я не знаю, почему он не предупреждает).
Вы получаете предупреждение с двумя, потому что компилятор знает значения в операнде. Выходы правильные, потому что оба используют /b
который без знака долго. Временное значение делится на b
должен быть больше или равен диапазону типов данных, ( i * j )
или же ( 100 * 30000000 )
хранятся в регистре ЦП, который имеет тот же диапазон типов данных, что и значение, которое нужно разделить, если b
был int
временный результат будет int
, поскольку b
является ulong, int не может быть разделен на ulong, временное значение сохраняется в ulong.
Это неопределенное поведение, если оно переполняется, но в этих случаях оно не переполняется
Программа с той же структурой, только меняющаяся b
в int
будет иметь только две строки в коде .s.
cltd
idivl (%ecx)
к b = int
movl $0,
%edx divl (%ecx)
b = длинная без знака,
idivl выполняет подписанное деление, сохраняя значение как подписанное
divl выполняет беззнаковое деление, сохраняя значение как беззнаковое
Итак, вы правы, операция переполнена, вывод правильный из-за операции деления.
Что касается 5/4, результат — неопределенное поведение.
Однако обратите внимание, что если вы изменили типы на unsigned (для констант просто добавьте u
суффикс) не только значения соответствуют, но согласно 3.9.1 / 4 арифметика становится модульной арифметикой, и результат отлично определяется даже для больших промежуточных значений, которые не подходит по типу.