4

Недавно я столкнулся с проблемой на 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 является 32b, long long является 64b, и они оба следуют IEEE правила):

  • Для линии № 2, скобка сначала вычисляется, используя int S в качестве промежуточных результатов , что приводит к переполнению и хранению -1073741824. Этот промежуточный результат повышается до long long для последнего умножения, и поэтому результат печати должен быть -5368709120.
  • Строки №1 и №3 являются «эквивалентными», так как порядок оценки не определен.

Теперь, для строк №1 и №3 здесь я был не уверен: я думал, что хотя порядок оценки не определен, компилятор будет «продвигать» все операции в тип самого большого операнда, а именно long long. Таким образом, нет переполнения не произойдет в этом случае, так как все расчеты будут сделаны в 64b ... Но это то, что GCC 5.3.0 дает мне для этого кода:

~/tmp$ g++-5 cast.cc 
~/tmp$ ./a.out 
-5368709120 
-5368709120 
16106127360 

Я бы ожидать 16106127360 для первого результата слишком. Поскольку я сомневаюсь, что в GCC имеется ошибка компилятора такого масштаба, я думаю, что ошибка лежит между клавиатурой и стулом.

Может ли кто-нибудь подтвердить или подтвердить, что это неопределенное поведение, и GCC правильно дает мне все, что он дает (так как это не определено)?

+3

двоичный оператор * имеет лево-правовую ассоциативность – PeterT

+0

работает аналогично в java.не бросается, пока не дойдет до длинного, слева направо :) – eiran

+0

Обратите внимание, что переполнение - это неопределенное поведение. Вероятно, вы просто собираетесь обернуть, но это необязательно, и компилятор может делать всевозможные странные вещи, особенно с такими константами, как вы: http://stackoverflow.com/questions/3948479/integer-overflow-and-undefined -behavior http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html –

ответ

7

GCC верен.

  1. Ассоциативность для умножения - left to right. Это означает, что все эти выражения оцениваются слева направо.
  2. Продвижение к более высокому типу доступно только между двумя операндами разных типов одиночный двоичный оператор.

Например, первое выражение анализируется как i * j * k * n * l = ((((i * j) * k) * n) * l) и продвижение происходит только тогда, когда последний из умножений вычисляется, но в этот момент левый операнд уже неверно.

3

Стандарт однозначно определяет группировку следующим образом:

5.6 Мультипликативные операторы [expr.mul]

1 мультипликативные операторы *, /, и% группы влево-вправо.

Это означает, что a * b * c оценивается как (a * b) * c. Соответствующий компилятор не имеет права оценивать его как a * (b * c).

Смежные вопросы