Насколько разрушительным является переполнение целых чисел в C++?

Мне было просто интересно, насколько разрушительным является целочисленное переполнение. Возьмите следующий пример программы:

#include <iostream>

int main()
{
    int a = 46341;
    int b = a * a;
    std::cout << "hello world\n";
}

поскольку a * a переполнения на 32-битных платформах, и целочисленное переполнение вызывает неопределенное поведение, есть ли у меня какие-либо гарантии, что hello world на самом деле появится на моем экране?


Я удалил "подписанную" часть из своего вопроса на основе следующих стандартных цитат:

(§5 / 5 C++ 03, §5 / 4 C++ 11) Если во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение не определено.

(§3.9.1 / 4) Целые числа без знака, объявленные unsigned, должен подчиняться законам арифметики по модулю 2^n, где n - количество битов в представлении значения этого конкретного размера целого числа. Это подразумевает, что арифметика без знака не переполняется, потому что результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю на число, которое на единицу больше наибольшего значения, которое может быть представлено результирующим целочисленным типом без знака.

3 ответа

Решение

Как отметил @Xeo в комментариях (сначала я поднял это в чате C++):
Неопределенное поведение действительно означает это, и оно может ударить вас, когда вы меньше всего этого ожидаете.

Лучший пример этого здесь: почему целочисленное переполнение в x86 с GCC вызывает бесконечный цикл?

В x86 целочисленное переполнение со знаком - это просто обход. Поэтому обычно вы ожидаете, что то же самое произойдет в C или C++. Тем не менее, компилятор может вмешаться - и использовать неопределенное поведение как возможность для оптимизации.

В примере, взятом из этого вопроса:

#include <iostream>
using namespace std;

int main(){
    int i = 0x10000000;

    int c = 0;
    do{
        c++;
        i += i;
        cout << i << endl;
    }while (i > 0);

    cout << c << endl;
    return 0;
}

При компиляции с GCC GCC оптимизирует тест цикла и делает его бесконечным циклом.

Вы можете активировать некоторые функции безопасности оборудования. Так что нет, у вас нет никаких гарантий.

Изменить: Обратите внимание, что gcc имеет -ftrapv вариант (но, похоже, он не работает для меня).

Есть два взгляда на неопределенное поведение. Существует мнение, что его нужно собирать для странного оборудования и других особых случаев, но обычно оно должно вести себя разумно. И есть мнение, что все может случиться. И в зависимости от источника UB некоторые придерживаются разных мнений.

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

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

Другие вопросы по тегам