Это неопределенное поведение, если промежуточный результат выражения переполняется?

Этот вопрос является результатом другого ТАК вопрос.

Пример кода

#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. Правильно ли я считаю, что #1 а также #2 вызвать неопределенное поведение, потому что промежуточный результат 100 * 30000000 не вписывается в int? Или вывод, который я вижу, четко определен?

  2. Почему я получаю только предупреждение с #2?

5

Решение

Да, это неопределенное поведение, и результат, который вы получаете, обычно… unsigned long это 64-битный тип.

U Это UB, поэтому нет никаких гарантий.

3

Другие решения

Промежуточный результат

Да, это неопределенное поведение. Что делать, если вы просто остановились прямо там и return m? Компилятор должен перейти из точки A в точку B, и вы сказали ему сделать это, выполнив этот расчет (что невозможно). Компилятор может решить оптимизировать этот оператор таким образом, чтобы избежать переполнения, но, насколько я знаю, стандарт не требует от оптимизатора что-либо делать.

Почему нет ошибки, когда они переменные?

Вы явно говорите gcc не оптимизировать вообще (-O0), поэтому я предполагаю, что он не знает значений i а также j в таком случае. Обычно вы изучаете значения из-за постоянное складывание, но, как я уже сказал, вы сказали не оптимизировать.

Если вы перезапустите это, а оно все еще не упомянет, существует также вероятность того, что это предупреждение будет сгенерировано перед запуском оптимизатора, поэтому оно просто недостаточно умно, чтобы вообще делать постоянное свертывание для этого шага.

2

1) Да, это неопределенное поведение.

2) Поскольку # 1 включает в себя переменные (не константы), поэтому компилятор вообще не знает, будет ли он переполнен (хотя в этом случае это происходит, и я не знаю, почему он не предупреждает).

2

Вы получаете предупреждение с двумя, потому что компилятор знает значения в операнде. Выходы правильные, потому что оба используют /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 выполняет беззнаковое деление, сохраняя значение как беззнаковое

Итак, вы правы, операция переполнена, вывод правильный из-за операции деления.

В чем разница между idivl и divl?

https://stackoverflow.com/a/12488534/1513286

1

Что касается 5/4, результат — неопределенное поведение.

Однако обратите внимание, что если вы изменили типы на unsigned (для констант просто добавьте u суффикс) не только значения соответствуют, но согласно 3.9.1 / 4 арифметика становится модульной арифметикой, и результат отлично определяется даже для больших промежуточных значений, которые не подходит по типу.

0
По вопросам рекламы [email protected]