TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #445 4.78K
​​Целочисленные переполнения

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

Для беззнаковых типов тут довольно просто. Переполнение переменных этих типов нельзя в полной мере назвать переполнением, потому что для них все операции происходят по модулю 2^N. При "переполнении" беззнакового числа происходит его уменьшение с помощью деления по модулю числа, которое на 1 больше максимально доступного значения данного типа(то есть 2^N, N - количество доступных разрядов). Но это скорее не математическая операция настоящего деления по модулю, а следствие ограниченного размера ячейки памяти. Чтобы было понятно, сразу приведу пример.

Вот у нас есть число UINT32_MAX. Его бинарное представление - 32 единички. Больше просто не влезет. Дальше мы пробуем прибавить к нему единичку. Чистая и незапятнанная плотью компьютеров математика говорит нам, что в результате должно получится число, которое состоит из единички и 32 нулей. Но у нас в распоряжении всего 32 бита. Поэтому верхушка просто отрезается и остаются только нолики.

Захотим мы допустим пятерку, бинарное представление которой это 101, прибавить к UINT32_MAX. Произойдет опять переполнение. В начале мы берем младший разряд 5-ки и складываем его с UINT32_MAX и уже переполненяемся, получаем ноль. Осталось прибавить 100 в двоичном виде к нолю и получим 4. Как и полагается.

И здесь поведение определенное, известное и стандартное. На него можно положиться.

Но вот что со знаковыми числами?

Стандарт говорит, что переполнение знаковых целых чисел - undefined behaviour. Но почему?

Ну как минимум потому что стандарт отдавал на откуп компиляторам выбор представления отрицательных чисел. Как ранее мы обсуждали, выбирать приходится между тремя представлениями: обратный код, дополнительный код и метод "знак-амплитуда".

Так вот во всех трех сценариях результат переполнения будет разный!

Возьмем для примера дополнительный код и 4-х байтное знаковое число. Ноль выглядит, как 000...00, один как 000...01 и тд. Максимальное значение этого типа INT_MAX выглядит так: 0111...11 (2,147,483,647). Но! Когда мы прибавляем к нему единичку, то получаем 100...000, что переворачиваем знаковый бит, число становится отрицательным и равным INT_MIN.

Однако для обратного кода те же рассуждения приводят к тому, что результатом вычислений будет отрицательный ноль!

Ситуация здесь на самом деле немного поменялась с приходом С++20, который сказал нам, что у нас теперь единственный стандартный способ представления отрицательных чисел - дополнительный код. Об этих изменениях расскажу в следующем посте.

Don't let the patience cup overflow. Stay cool.

#cpp20 #compiler #cppcore
  • ❤ 24
  • 👍 8
  • 🔥 4
  • ❤‍🔥 3
More from @grokaemcpp
  1. Oct 14, 2026​​Что внутри std::condition_variable? #опытным Более менее все знакомы с примитивом синхро…
  2. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  3. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  4. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  5. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  6. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →