Целочисленное переполнение
Целочисленное переполнение — это ситуация, когда в результате арифметической операции над целыми числами получается значение, выходящее за пределы диапазона, которое может быть представлен с помощью заданного количества разрядов — либо больше максимального, либо меньше минимального представимого значения.
История и происхождение
Целочисленное переполнение возникает, когда результат арифметической операции не укладывается в диапазон, представимый заданным количеством разрядов. В программировании используются, как правило, двоичные целые числа, однако переполнение возможно в любой позиционной системе счисления с ограниченным количеством знаков. Например, для шестиразрядной десятичной системы: 999999 + 1 приведёт к невозможному результату. Аналогично, для четырёхразрядной двоичной — 1111 + 1 также приведёт к выходу за границы. В обоих случаях требуется дополнительный разряд для представления результата, но он отсутствует.
Во всех средах программирования целые числа ограничены максимальным и минимальным значением, определяемым шириной разрядности и признаком знаковости (signed/unsigned). Стандартный тип целого числа зависит от платформы и используемого языка программирования. Кроме стандартных существуют целые с уменьшенной ([short]) или увеличенной ([long]) разрядностью. Также применяются целые с произвольной точностью (англ. arbitrary-precision), ограниченные предустановленной точностью или объёмом памяти.
Большинство АЛУ аппаратно работают с беззнаковыми числами. Для поддержки отрицательных чисел используется абстракция знакового типа, чаще всего — представление в дополнительном коде (two's complement). Например, знаковое 32-битное целое использует старший бит для знака, а остальные 31 бит — для значения числа. Если при операции происходит перенос выше 31-го бита, знак затирается, и получается переполнение; аппаратное устройство не контролирует это — обнаружение и обработка переполнения остаётся на уровне программы.
При использовании беззнаковых целых максимальной ширины АЛУ, результат не может быть шире регистра; АЛУ при этом устанавливает флаг переноса. При установленном флаге программа может распознать факт переполнения.
Обработка переполнения требует специальной логики: если результат оказался повреждён, могут возникать дополнительные ошибки в вычислениях.
Использование операций над целыми, совпадающими по разрядности с АЛУ, как правило, наиболее производительно. Для работы с числовыми типами, превышающими разрядность регистра, применяются расширения SIMD, например SSE2 для 64-битных операций на x86, AVX — до 512 бит[1].
| Биты | Названия | Диапазон | Диапазон знаковых | Диапазон беззнаковых |
|---|---|---|---|---|
| 8 бит | байт[2], sbyte[3], октет | 28 − 1 | −128[4] | 0[5] |
| 127[6] | 255[7] | |||
| 16 бит | word, short, int16[8], uint16[9] | 216 − 1 | −32 768[10] | 0[11] |
| 32 767[12] | 65 535[13] | |||
| 32 бита | int32[14], uint32[15] | 232 − 1 | −2 147 483 648[16] | 0[17] |
| 2 147 483 647[18] | 4 294 967 295[19] | |||
| 64 бита | int64[20], uint64[21] | 264 − 1 | −9 223 372 036 854 775 808[22] | 0[23] |
| 9 223 372 036 854 775 807[24] | 18 446 744 073 709 551 615[25] | |||
| 128 бит | int128[26], uint128[27] | 2128 − 1 | −170 141 183 460 469 231 731 687 303 715 884 105 728 | 0 |
| 170 141 183 460 469 231 731 687 303 715 884 105 727 | 340 282 366 920 938 463 463 374 607 431 768 211 455 |
Когда результат беззнаковой операции превышает максимальное значение для N-битного целого, происходит переполнение — результат выражается по модулю 2N, «оборачивается», остаются только младшие биты.
Например, при сложении 8-битных чисел: 255 + 2 = 1 (так как 257 mod 256 = 1), а 0 − 1 = 255 (что в дополнительном коде соответствует −1).
Оборачиваемое переполнение может приводить к проблемам безопасности: если переполненное значение используется как размер выделяемого буфера, выделяется меньший буфер, что может привести к переполнению буфера и выполнению произвольного кода.
Если для переменной используется знаковый тип, программа может предполагать всегда положительное значение. Переполнение может привести к переходу в область отрицательных чисел, нарушая предположения, что способствует ошибкам. Например, 127 + 1 = −128 для 8-битного знакового. Для предотвращения таких ошибок рекомендуется использовать беззнаковые типы для значений, которые не должны быть отрицательными.
Флаги переполнения
Большинство процессоров имеют два флага для контроля переполнения.
Флаг переноса (carry flag) устанавливается, если результат сложения или вычитания (для беззнаковых чисел) не укладывается в указанный битовый разряд — это указывает на переполнение с переносом или заимствованием из старшего бита. Этот флаг используется при выполнении многоразрядных сложений по частям.
Флаг переполнения (overflow flag) выставляется, если результат операции над знаковыми числами не соответствует ожидаемому знаку (например, отрицательный результат при сложении двух положительных чисел), что сигнализирует о переполнении диапазона знаковых чисел.
Варианты определения и неоднозначности
Для беззнаковых типов часто термин «переполнение» употребляют по отношению к оборачиванию результата, однако стандарт C11 указывает, что для беззнаковых типов это не переполнение[28].
Если результат операции ограничивается пределами (насыщающая арифметика), такое событие называют насыщением. Для уточнения рекомендуется использовать термины «оборачиваемое переполнение» (англ. wrapping overflow)[29] и «насыщающее переполнение» (англ. saturating overflow)[30].
Термин «целочисленное недополнение» некорректен: переполнение связано только с границей по разрядам, а не со знаком числа. Хотя в литературе встречается термин integer underflow, он не имеет строгого основания, правильнее говорить о переполнении, выходе за границы массива или ошибках приведения типов.[31][32][33][34] Истинное арифметическое недополнение возможно только для операций с плавающей запятой.
При преобразованиях типов и операциях с пограничными случаями, термин переполнение может быть неоднозначен. Например, преобразование числа 127,25 в тип с максимумом 127: при округлении к нулю результат может не считаться переполнением по стандарту C11[28].
Несогласованное поведение
Способы предотвращения и обработки целочисленного переполнения
| Язык | Беззнаковые целые | Знаковые целые |
|---|---|---|
| Ada | по модулю диапазона типа | raise Constraint_Error |
| C, C++ | по модулю степени 2 | неопределённое поведение |
| C# | по модулю 2 в блоке unchecked; исключение System.OverflowException в блоке checked[36]
| |
| Java | по модулю степени 2 (char — единственный беззнаковый) | по модулю степени 2 |
| JavaScript | все числа — с плавающей точкой двойной точности, кроме BigInt | |
| MATLAB | встроенные целые — насыщаются; фиксированные — конфигурируются (оборачивание/насыщение) | |
| Python 2 | — | автоматический переход к типу long |
| Scheme | — | переход к bigNum |
| Simulink | конфигурируется на оборачивание или насыщение | |
| Smalltalk | — | переход к LargeInteger |
| Swift | ошибка, если не используются специальные операторы переполнения[37] | |
Для обнаружения переполнения во время выполнения применяются, например, средства UBSan в компиляторах C.
В Java 8 реализованы перегруженные методы, такие как англ. addExact(int, int), которые возбуждают исключение англ. ArithmeticException при переполнении.
Компьютерная команда реагирования на инциденты CERT разработала модель AIR (As-if Infinitely Ranged) для автоматической ликвидации переполнений и усечения целых в C/C++ посредством обработки ошибок во время выполнения[38].
Выделяя под переменные тип с достаточным диапазоном для всех возможных значений и контролируя порядок вычислений, часто можно избежать переполнения. Статический анализ, формальная верификация, проектирование по контракту используются для предотвращения ошибок переполнения.
Если переполнение возможно, в программе можно вставить проверки результата. Например, при вводе пользовательских данных важно остановить выполнение при переполнении, чтобы избежать продолжения работы с некорректными результатами.
В аппаратуре поддерживается обработка многобайтовых операций с контролем флагов переноса (многоточечная арифметика).
Обработка переполнения может осуществляться как до вычисления (проверка возможности возникновения), так и после (анализ результата на допустимость). Для переносимости предпочтительно тестировать заранее.
В некоторых языках (например Ada) и функциональных системах по умолчанию возбуждается исключение при переполнении, в других (например Python с версии 2.4) используется динамическое расширение числа до long.
Языки с поддержкой арифметики произвольной точности и безопасностью типов (Python, Smalltalk, Common Lisp) автоматически увеличивают разрядность при необходимости, либо возбуждают исключения. Однако целочисленное переполнение может возникать и здесь — при явной оптимизации критичных блоков кода, например, аннотируя переменную как fixnum (машинное целое) в Common Lisp[39][40][41][42][43][44].
Современные языки, такие как Rust, предоставляют функции для явного управления обработкой переполнения (checked/unchecked/wrapping/saturating операции).
В компьютерной графике и обработке сигналов часто используются данные в диапазоне от 0 до 1 или от −1 до 1 (например, оттенки серого). Насыщающая арифметика позволяет автоматически ограничить яркость или координату до предельно допустимого значения, вместо оборачивания результата.
Примеры
Неожиданное арифметическое переполнение — типичная причина программных ошибок. Такие баги могут долго не замечаться, так как проявляются лишь при больших значениях данных.
Пример: вычисление среднего двух чисел как (a+b)/2 может привести к переполнению суммы даже при допустимом результате среднего[45].
В 1985–1987 годах ошибка переполнения в медицинских аппаратах Therac-25 приведла к гибели шести пациентов из-за передозировки облучения[46].
Переполнение стало причиной аварии ракеты Ariane 5 в 1996 году: необработанное переполнение в коде управления двигателями привело к крушению носителя[47]. Программный модуль, вызвавший ошибку, даже не требовался для данного запуска — он был унаследован от предыдущей версии программного обеспечения. Диагностический дамп, вызванный ошибкой переполнения, напрямую управлял исполнительным устройством, вызывая потерю управляемости ракеты.
В апреле 2015 года Федеральное управление гражданской авиации США предписало операторам Boeing 787 периодически перезагружать электросистему, чтобы избежать переполнения целочисленного счётчика, приводящего к потере питания; компания Boeing выпустила обновление ПО[48][49]. Ошибка возникала после истечения 231 сотых секунды (около 248 дней), что указывает на использование 32-битного знакового целого.
Переполнение встречается и в видеоиграх. В Super Mario Bros. счётчик жизней реализован как знаковый байт (от −128 до 127): при достижении 128 жизней происходит обнуление, что приводит к мгновенному завершению игры при следующей смерти. Аналогичные баги присутствовали в ряде классических игр (например, Donkey Kong, Pac-Man[50], Minecraft[51]).
IBM–Microsoft Macro Assembler (MASM) версии 1.00 и другие программы, скомпилированные с использованием того же Pascal-компилятора, имели ошибку переполнения и неверной интерпретации знака целого числа при инициализации стека, из-за чего не запускались на некоторых новых машинах с DOS и с большим объёмом памяти[52].
В 2016 году в казино Resorts World из-за бага переполнения игровой автомат выдал выигрыш $42 949 672,76; после разбирательства комиссия признала это ошибкой программного обеспечения и аннулировала выигрыш, поскольку максимальная выплата была явно ограничена правилами[53].
Примечания
- ↑ Intel® AVX-512 — Fast Modular Multiplication Technique (англ.). Intel. Дата обращения: 29 июня 2024. Архивировано 22 июня 2025 года.
- ↑ .NET Byte Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET SByte Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET SByte.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Byte.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET SByte.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Byte.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int16 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt16 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int16.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt16.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int16.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt16.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int32 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt32 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int32.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt32.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int32.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt32.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int64 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt64 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int64.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt64.MinValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int64.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt64.MaxValue Field (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET Int128 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ .NET UInt128 Struct (англ.). Microsoft Docs. Дата обращения: 29 июня 2024.
- ↑ 1 2 ISO/IEC 9899:2011 Information technology - Programming languages - C (англ.). ANSI.org. Дата обращения: 29 июня 2024. Архивировано 3 февраля 2012 года.
- ↑ Wrap on overflow — MATLAB & Simulink (англ.). MathWorks. Дата обращения: 29 июня 2024. Архивировано 14 января 2018 года.
- ↑ Saturate on overflow — MATLAB & Simulink (англ.). MathWorks. Дата обращения: 29 июня 2024. Архивировано 29 ноября 2022 года.
- ↑ CWE - CWE-191: Integer Underflow (Wrap or Wraparound) (англ.). CWE. Дата обращения: 29 июня 2024. Архивировано 4 октября 2025 года.
- ↑ Le Blanc, David. 24 Deadly Sins of Software Security. — 2005. — P. 120.
- ↑ Avoiding Buffer Overflows and Underflows (англ.). developer.apple.com. Дата обращения: 29 июня 2024. Архивировано 6 октября 2017 года.
- ↑ Integer underflow and buffer overflow processing MP4 metadata in libstagefright (англ.). Mozilla. Дата обращения: 29 июня 2024. Архивировано 20 июня 2025 года.
- ↑ Operator expressions — The Rust Reference (англ.). Rust. Дата обращения: 29 июня 2024. Архивировано 4 октября 2025 года.
- ↑ BillWagner Checked and Unchecked (C# Reference) (англ.). Microsoft Docs (8 апреля 2023). Дата обращения: 29 июня 2024. Архивировано 30 октября 2008 года.
- ↑ The Swift Programming Language. Swift 2.1 Edition. 21 октября 2015.
- ↑ As-if Infinitely Ranged Integer Model (англ.). cert.org. Дата обращения: 29 июня 2024. Архивировано 16 октября 2009 года.
- ↑ Declaration TYPE (англ.). Common Lisp HyperSpec. Дата обращения: 29 июня 2024. Архивировано 19 февраля 2005 года.
- ↑ Declaration OPTIMIZE (англ.). Common Lisp HyperSpec. Дата обращения: 29 июня 2024. Архивировано 19 июля 2025 года.
- ↑ Reddy, Abhishek Features of Common Lisp (англ.). random-state.net (22 августа 2008). Дата обращения: 29 июня 2024. Архивировано 22 июня 2025 года.
- ↑ Pierce, Benjamin C. Types and Programming Languages : [англ.]. — MIT Press, 2002. — ISBN 0-262-16209-1.
- ↑ Wright, Andrew K.; Felleisen, Matthias (1994). “A Syntactic Approach to Type Soundness”. Information and Computation [англ.]. 115 (1): 38—94. DOI:10.1006/inco.1994.1093.
- ↑ Macrakis, Stavros (апрель 1982). “Safety and power”. ACM SIGSOFT Software Engineering Notes [англ.]. 7 (2): 25—26. DOI:10.1145/1005937.1005941. Проверьте дату в
|date=(справка на английском) - ↑ Extra, Extra — Read All About It: Nearly All Binary Searches and Mergesorts are Broken (англ.). Google Official Blog (2 июня 2006). Дата обращения: 29 июня 2024. Архивировано 15 июня 2012 года.
- ↑ Beuhler, Patrick When Small Software Bugs Cause Big Problems (англ.). Grio Blog (5 июля 2021). Дата обращения: 29 июня 2024. Архивировано 7 сентября 2025 года.
- ↑ Gleick, James. A Bug and A Crash (англ.), The New York Times (1 декабря 1996). Архивировано 22 июня 2025 года. Дата обращения: 29 июня 2024.
- ↑ Mouawad, Jad. F.A.A. Orders Fix for Possible Power Loss in Boeing 787 (англ.), The New York Times (30 апреля 2015). Архивировано 3 мая 2015 года. Дата обращения: 29 июня 2024.
- ↑ US-2015-09-07: Electrical Power – Deactivation (англ.). Airworthiness Directives. European Aviation Safety Agency (4 мая 2015). Дата обращения: 29 июня 2024. Архивировано 10 декабря 2024 года.
- ↑ Pittman, Jamey The Pac-Man Dossier (англ.). Дата обращения: 29 июня 2024. Архивировано 26 февраля 2009 года.
- ↑ Far Lands (англ.). Minecraft Wiki. Дата обращения: 29 июня 2024. Архивировано 19 ноября 2025 года.
- ↑ Lenclud, Christophe Debugging IBM MACRO Assembler Version 1.00 (англ.) (21 апреля 2017). Дата обращения: 29 июня 2024.
- ↑ Kravets, David Sorry ma'am you didn't win $43M — there was a slot machine 'malfunction' (англ.). Ars Technica (15 июня 2017). Дата обращения: 29 июня 2024. Архивировано 5 сентября 2025 года.
Литература
- Pierce, Benjamin C. (2002). “Types and Programming Languages” [англ.]. MIT Press. ISBN 0-262-16209-1. Дата обращения 2024-06-29.
- Wright, Andrew K.; Felleisen, Matthias (1994). “A Syntactic Approach to Type Soundness”. Information and Computation [англ.]. 115 (1): 38—94. DOI:10.1006/inco.1994.1093. Дата обращения 2024-06-29.
- Macrakis, Stavros (апрель 1982). “Safety and power”. ACM SIGSOFT Software Engineering Notes [англ.]. 7 (2): 25—26. DOI:10.1145/1005937.1005941. Проверьте дату в
|date=(справка на английском)