Недавно я столкнулся с проблемой, связанной с моим кодом на C ++, который заставил меня задуматься, не понял ли я, что компилятор будет делать с длинными операциями …
Просто посмотрите на следующий код:
#include <iostream>
int main() {
int i = 1024, j = 1024, k = 1024, n = 3;
long long l = 5;
std::cout << i * j * k * n * l << std::endl; // #1
std::cout << ( i * j * k * n ) * l << std::endl; // #2
std::cout << l * i * j * k * n << std::endl; // #3
return 0;
}
Для меня порядок, в котором умножения будут происходить в любой из этих трех строк, не определен. Тем не менее, вот что я думал, что произойдет (при условии, int
это 32б, long long
64b, и они оба следуют правилам IEEE):
int
в качестве промежуточных результатов, приводящих к переполнению и сохранению -1073741824. Этот промежуточный результат повышен до long long
поэтому для последнего умножения и напечатанного результата должно быть -5368709120.Теперь для строк № 1 и № 3 я был не уверен: я думал, что, хотя порядок вычисления не определен, компилятор «продвинет» все операции до типа наибольшего операнда, а именно long long
Вот. Следовательно, в этом случае переполнения не произойдет, поскольку все вычисления будут выполняться в 64b … Но вот что GCC 5.3.0 дает мне для этого кода:
~/tmp$ g++-5 cast.cc
~/tmp$ ./a.out
-5368709120
-5368709120
16106127360
Я бы ожидал 16106127360 для первого результата тоже. Поскольку я сомневаюсь, что в GCC есть ошибка компилятора такого масштаба, я предполагаю, что ошибка лежит между клавиатурой и стулом.
Может ли кто-нибудь подтвердить / подтвердить, что это неопределенное поведение, и GCC правильно дал мне все, что он дает (так как это не определено)?
GCC правильно.
Например, первое выражение анализируется как i * j * k * n * l = ((((i * j) * k) * n) * l)
и продвижение происходит только тогда, когда вычисляется последнее из умножений, но в этот момент левый операнд уже неверен.
Стандарт однозначно определяет группировку следующим образом:
5.6. Мультипликативные операторы [expr.mul]
1 Мультипликативные операторы *, / и% group слева направо.
Это означает, что a * b * c
оценивается как (a * b) * c
, Соответствующий компилятор не может свободно оценивать его как a * (b * c)
,