EnglishРусский Map

Знаковые и беззнаковые размеры

title
Знаковые и беззнаковые размеры
type
concept
summary
Выбор между знаковыми и беззнаковыми типами для размеров: классы багов для каждого варианта и опыт разных языков программирования
tags
language-design, type-systems
created
2026-05-03
updated
2026-05-03
lang
ru
source_updated
2026-05-03
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

Выбор типа по умолчанию для размеров, длин и индексов: знаковый или беззнаковый. В C его сделали беззнаковым (size_t), и большинство наследников скопировали этот подход; в Java от беззнаковых типов отказались вовсе; в Go выбрали знаковый; C3 перешёл с беззнакового на знаковый в 2026 году после пяти лет с беззнаковым по умолчанию.

Это решение распространяется по всей системе типов: беззнаковые размеры влекут за собой беззнаковую индексацию, а это означает беззнаковую арифметику в любом коде, работающем с длинами массивов. Отсюда постоянное смешивание знаковых и беззнаковых типов, вынуждающее язык полагаться либо на неявные приведения (тихие баги), либо на повсеместные явные приведения (культура спама cast'ами).

Классы багов, которые порождает беззнаковый тип по умолчанию

  • Бесконечный цикл при обратном отсчёте. for (uint x = 10; x >= 0; x--) - условие никогда не станет ложным. Паттерн настолько распространён, что в C3 сделали >= 0 для беззнаковых типов отдельной ошибкой компиляции.
  • Инверсия сравнения знакового и беззнакового. uint a = 0; int b = -1; if (a > b) - C повышает тип до беззнакового, поэтому b превращается в огромное положительное число, и результат сравнения неверен. Языкам с неявным приведением типов нужно явное правило "безопасного сравнения", чтобы это исправить.
  • Правдоподобное переполнение. Знаковое переполнение даёт явно некорректное отрицательное число; беззнаковое переполнение даёт правдоподобное положительное число, которое просто неверно. В беззнаковом варианте ошибка часто проскакивает поверхностное тестирование.
  • Переход через границу в кольцевом буфере. ((start - offset_back) % length + length) % length - естественный паттерн с отрицательным смещением тихо ломается на беззнаковых значениях, и никакое правило системы типов не способно это отловить. Корректный беззнаковый вариант выглядит куда более громоздко и неестественно.
  • Асимметрия деления и остатка от деления. (foo + a) % 2 даёт один результат, когда foo знаковый, и совершенно другой, когда тип повышается до беззнакового. Даже с правилами безопасного приведения / и % обнажают знаковость так, как этого не делают +, - и *.

Классы багов, которые порождает знаковый тип по умолчанию

  • Попытки использовать отрицательный индекс массива. Отлавливаются во время выполнения при проверке границ; без более выразительной системы типов язык не может поймать их статически.
  • Ошибка на единицу около нуля. Возможна, но результатом обычно становится обычное отрицательное число, а не переполнение с уходом в миллиарды.
  • Теоретическая потеря диапазона. Половина диапазона значений уходит под отрицательные числа, которые в основном не нужны. На 64 битах память закончится задолго до достижения 2^63, так что потерянная половина роли не играет. На 32 битах адресация дальше 2 ГБ требуется настолько редко, что в языках вроде Go с этим просто смирились.

Кто что выбрал

Язык Размеры Примечания
C беззнаковые (size_t) Выбор, который разошёлся по остальным
C++ беззнаковые Унаследовано из C; за знаковую индексацию выступали Страуструп и Саттер
Rust беззнаковые (usize) Переполнение вызывает ошибку во время выполнения, а не wrap
Zig беззнаковые (usize) То же соглашение, что и в Rust
Java только знаковые Беззнаковых типов нет вообще
Go знаковые (int) Выбор команды, знавшей проблемы C++ на собственном опыте
C3 знаковые (sz) с версии 0.8.0 Перешли в 2026 году после 5 лет с беззнаковыми

Дихотомия: спам cast'ами против минимизации приведений

Отдельный, но связанный выбор: когда встречаются знаковый и беззнаковый типы, язык выполняет неявное приведение или требует явного cast'а?

  • Подход со спамом cast'ами. Приведения типов писать несложно, "они документируют преобразование". Минус: cast, корректный на момент написания, начинает молча отсекать биты при изменении вышележащего типа. Приведения превращаются в способ "заглушить все предупреждения".
  • Подход с минимизацией cast'ов. Явные приведения означают опасную зону. В теории это чище, но для таких правил сложнее выработать спецификацию. C3 пошёл по этому пути, из-за чего проблема беззнаковых размеров встала в полный рост: беззнаковые размеры требуют постоянных преобразований между знаковыми и беззнаковыми типами, а язык, стремящийся минимизировать cast'ы, вынужден делать большинство таких преобразований неявными.

Показателен опыт Rust: беззнаковые размеры вкупе с принципом "явные приведения везде" приводят к кодовым базам, переполненным вызовами as usize и as i64. Вставленные машинально, они накапливаются и на практике не ловят баги, которые должны были документировать.

Аргумент "беззнаковый тип кодирует предусловие"

Логика выбора беззнаковых типов по умолчанию строится на том, что "это значение не может быть отрицательным" - полезное предусловие, и беззнаковый тип фиксирует его в системе типов. Этот довод не выдерживает проверки инструментами верификации: беззнаковые типы кодируют "значение заворачивается по модулю 2^N", а не "значение лежит в диапазоне [0, N)". Предусловие всегда оставалось неформальным соглашением, а "гарантия" со стороны системы типов оказывается вовсе не той гарантией.

Наблюдение о привыкании к скрытым издержкам

Самый любопытный нетехнический вывод из ретроспективы C3 заключается в том, что опытные программисты на C/C++ перестают замечать цену повсеместного использования беззнаковых типов: они несут эту когнитивную нагрузку на автомате и забывают о её существовании. Это аргумент о лени как добродетели, применённый наоборот: дисциплина, компенсирующая изъян инструмента, становится невидимой, и устранение изъяна воспринимается как отказ от дисциплины.

См. также

  • unsigned-sizes-c3-mistake - канонический подробный пример этой концепции
  • c3-lang - разбор на примере реального языка
  • zig, zig-functional-programmers - в Zig сохранили беззнаковые размеры; полезный контраст
  • type-systems-vocabulary - смежные споры о терминологии систем типов
  • no-silver-bullet - компромиссы при проектировании языков в целом