Открыть сервис

Переполнение целочисленного типа

Переполнение целочисленного типа — это ситуация, возникающая в компьютерных вычислениях, когда результат арифметической операции выходит за пределы диапазона значений, представимых данным целочисленным типом данных. В зависимости от языка программирования, архитектуры процессора и настроек компилятора, переполнение может приводить к неопределённому поведению, корректному циклическому переходу (wrap-around) или выбрасыванию исключения.

Причины возникновения

Переполнение целочисленного типа происходит из-за конечного размера ячейки памяти, выделяемой для хранения целого числа. В современных вычислительных системах целые числа представляются в двоичном виде с фиксированным количеством бит. Например, тип int (32 бита) может хранить значения от −2 147 483 648 до 2 147 483 647 (для знакового представления) или от 0 до 4 294 967 295 (для беззнакового). Любая операция, результат которой выходит за эти границы, вызывает переполнение.

Арифметические операции

Наиболее частые причины — сложение, вычитание, умножение, деление и возведение в степень. Например, сложение двух больших положительных чисел может дать результат, превышающий максимальное значение типа. В знаковых типах это часто приводит к смене знака результата на отрицательный (так называемый «переход через ноль»).

Циклические операции

В некоторых языках (например, в C и C++) для беззнаковых типов переполнение определено как циклический переход (modulo arithmetic). Это означает, что значение уменьшается или увеличивается на величину, равную размеру диапазона. Например, для беззнакового 8-битного числа (0–255) 255 + 1 даёт 0, а 0 − 1 даёт 255.

Классификация

Переполнение целочисленного типа можно разделить по нескольким критериям.

По типу данных

  • Знаковое переполнение — происходит в типах, поддерживающих отрицательные числа (например, signed int). В языках C и C++ такое переполнение считается неопределённым поведением (undefined behavior). Компилятор может оптимизировать код, предполагая, что такого переполнения никогда не произойдёт, что может приводить к непредсказуемым результатам.
  • Беззнаковое переполнение — происходит в типах, хранящих только неотрицательные числа (например, unsigned int). В большинстве языков (C, C++, Java) оно строго определено как циклический переход по модулю 2^n, где n — количество бит.

По направлению

  • Переполнение вверх — результат превышает максимальное значение типа.
  • Переполнение вниз (underflow) — результат становится меньше минимального значения типа (для знаковых типов — меньше отрицательного минимума; для беззнаковых — меньше нуля, что приводит к циклическому переходу к максимальному значению).

По контексту

  • Арифметическое переполнение — при вычислениях.
  • Переполнение при преобразовании типов — когда значение одного типа приводится к другому, меньшего размера. Например, преобразование 32-битного числа 300 в 8-битный тип может дать 44 (остаток от деления на 256).
  • Переполнение при сдвигах — при битовых операциях сдвига, когда сдвиг на количество бит, превышающее размер типа, может быть неопределённым или циклическим.

Поведение в разных языках программирования

Реакция на переполнение целочисленного типа сильно варьируется.

C и C++

В C и C++ переполнение знаковых целых типов является неопределённым поведением. Компилятор может генерировать любой код, включая удаление проверок, игнорирование ошибок или даже аварийное завершение программы. Беззнаковые типы гарантируют циклический переход по модулю 2^n. Для обнаружения переполнения вручную используются специальные макросы или функции (например, __builtin_add_overflow в GCC).

Java

В Java переполнение целочисленных типов всегда определено как циклический переход (wrap-around) для всех типов, как знаковых, так и беззнаковых. Исключения не выбрасываются. Для обнаружения переполнения разработчики используют классы Math.addExact(), Math.subtractExact() и т.д., которые генерируют ArithmeticException при переполнении.

Python

Python поддерживает целые числа произвольной точности (big integers), поэтому переполнение в традиционном смысле не происходит. Число автоматически расширяется до необходимого размера, ограниченного только доступной памятью. Однако при преобразовании в типы с фиксированной длиной (например, через numpy или ctypes) переполнение возможно.

C#

В C# переполнение для целых типов по умолчанию также является циклическим, но его можно контролировать с помощью ключевого слова checked и настройки компилятора /checked. В checked-контексте переполнение вызывает исключение OverflowException.

Rust

В Rust переполнение целочисленных типов в режиме отладки (debug) вызывает панику (panic). В режиме выпуска (release) по умолчанию используется циклический переход, но это поведение можно изменить с помощью атрибутов или конфигурации.

Последствия и уязвимости

Переполнение целочисленного типа является источником многих программных ошибок и уязвимостей безопасности.

Ошибки в логике

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

Уязвимости безопасности

  • Целочисленное переполнение в буферах — классическая уязвимость, когда переполнение при вычислении размера буфера приводит к выделению слишком маленькой области памяти, что затем позволяет злоумышленнику записать данные за её пределами (buffer overflow).
  • Обход проверок — если размер данных проверяется с помощью целочисленного выражения, которое может переполниться, злоумышленник может обойти проверку. Например, if (size + 1 > MAX_SIZE) может быть обойдено, если size равно максимальному значению, после чего size + 1 станет нулём.
  • Денежные операции — в финансовых системах переполнение может приводить к зачислению или списанию некорректных сумм, что используется в атаках на криптовалютные кошельки или банковские системы.

Примеры известных уязвимостей

  • CVE-2014-0160 (Heartbleed) — хотя в основе лежала ошибка чтения буфера, целочисленное переполнение в расчёте длины пакета способствовало эксплуатации.
  • CVE-2015-7547 — уязвимость в glibc, связанная с переполнением при обработке DNS-ответов.
  • CVE-2021-44228 (Log4Shell) — в некоторых векторах эксплуатации использовалось целочисленное переполнение в обработчиках строк.

Методы предотвращения

Для защиты от переполнения целочисленного типа применяются различные подходы.

На уровне кода

  • Использование типов с проверкой — например, SafeInt в C++ или checked-контекст в C#.
  • Явные проверки перед операцией — сравнение операндов с границами диапазона перед выполнением арифметики.
  • Использование библиотек с произвольной точностью — например, BigInteger в Java или gmp в C++.
  • Применение статического анализаинструменты (например, Coverity, PVS-Studio) могут выявлять потенциальные переполнения.

На уровне компилятора

  • Включение предупреждений — компиляторы C/C++ (GCC, Clang) имеют флаги -Woverflow, -fsanitize=undefined для обнаружения переполнения во время выполнения.
  • Использование санитайзеровAddressSanitizer, UndefinedBehaviorSanitizer помогают выявить случаи неопределённого поведения.

На уровне архитектуры

  • Аппаратная поддержка — некоторые процессоры (например, x86 с флагом OF) сигнализируют о переполнении, что может быть использовано операционной системой или языком программирования.
  • Языки с безопасной типизацией — Rust, Swift, Go (с проверками) снижают риск переполнения за счёт строгих правил.

Интересные факты

  • В языке C переполнение знаковых целых типов является неопределённым поведением, что позволяет компиляторам выполнять агрессивные оптимизации. Например, компилятор может удалить проверку if (x + 1 > x), предполагая, что переполнение никогда не произойдёт, что может приводить к ошибкам в коде, который на это полагается.
  • В некоторых ранних компьютерных системах (например, на процессорах PDP-8) переполнение целочисленного типа приводило к аппаратному прерыванию, что позволяло операционной системе корректно обработать ситуацию.
  • В игровой индустрии переполнение иногда используется для создания нестандартных эффектов, например, в игре Super Mario Bros. (1985) переполнение счётчика жизней приводило к появлению отрицательного числа, что вызывало необычное поведение.

Источники

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →