Почему size_t неподписан?

Bjarne Stroustrup написал на языке программирования С++:

Беззнаковые целые типы идеально подходят для использования, которые обрабатывают хранилище как бит. Использование unsigned вместо int для получения еще одного бита для представляют собой целые положительные числа, которые почти никогда не являются хорошей идеей. Пытается убедитесь, что некоторые значения положительны, объявив переменные без знака обычно будут побеждены правилами неявного преобразования.

size_t кажется непознанным ", чтобы получить еще один бит для представления положительных целых чисел". Так это была ошибка (или компромисс), и если да, то следует ли нам минимизировать ее использование в нашем собственном коде?

Еще одна соответствующая статья Скотта Мейерса здесь. Подводя итог, он рекомендует не использовать unsigned в интерфейсах, независимо от того, всегда ли это значение положительно или нет. Другими словами, , даже если отрицательные значения не имеют смысла, вы не должны использовать unsigned.

Ответ 1

size_t неподписан по историческим причинам.

В архитектуре с 16-разрядными указателями, такими как "маленькое" DOS-программирование модели, было бы нецелесообразно ограничивать строки до 32 КБ.

По этой причине для стандарта C требуется (через требуемые диапазоны) ptrdiff_t, подписанный аналог size_t и тип результата разницы указателей, чтобы быть эффективно 17 бит.

Эти причины все еще могут применяться в некоторых частях встроенного мира программирования.

Однако они не применяются к современному 32-битовому или 64-битовому программированию, где гораздо более важно учитывать, что неудачные неявные правила преобразования C и С++ делают типы unsigned в аттракторах ошибок, когда они используются для числа (и, следовательно, арифметические операции и сравнения величин). С 20-20 назад мы можем теперь видеть, что решение принять те конкретные правила преобразования, где, например, string( "Hi" ).length() < -3 практически гарантирован, был довольно глупым и непрактичным. Однако это решение означает, что в современном программировании использование неподписанных типов для чисел имеет серьезные недостатки и никаких преимуществ; за исключением удовлетворения чувств тех, кто считает, что unsigned является самоописательным типом имени и не может думать о typedef int MyType.

Подводя итог, это не было ошибкой. Это было решение по очень разумным, практическим причинам программирования. Это не имело никакого отношения к переносу ожиданий с контролируемых границ языков, таких как Pascal, на С++ (что является ошибкой, но очень распространенной, даже если некоторые из тех, кто это делает, никогда не слышали о Pascal).

Ответ 2

size_t unsigned, потому что отрицательные размеры не имеют смысла.

(Из комментариев:)

Это не столько обеспечение, сколько утверждение того, что есть. Когда вы в последний раз видели список размером -1? Следуйте за этой логикой слишком далеко, и вы обнаружите, что unsigned не должно существовать вообще, и битовые операции также не должны допускаться. - geekosaur

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

Ответ 3

С другой стороны...

Миф 1: std::size_t является неподписанным из-за устаревших ограничений, которые больше не применяются.

Здесь часто упоминаются две "исторические" причины:

  • sizeof возвращает std::size_t, который был неподписанным с дней C.
  • Процессоры имели меньшие размеры слов, поэтому было важно сжать этот дополнительный бит диапазона.

Но ни одна из этих причин, несмотря на то, что они очень старые, фактически отнесена к истории.

sizeof все еще возвращает std::size_t, который по-прежнему без знака. Если вы хотите взаимодействовать с sizeof или стандартными библиотечными контейнерами, вам придется использовать std::size_t.

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

И хотя большинство основных вычислений выполняется на 32- и 64-разрядных процессорах, С++ по-прежнему используется на 16-разрядных микропроцессорах во встроенных системах даже сегодня. На этих микропроцессорах часто очень полезно иметь значение размера слова, которое может представлять любое значение в вашем пространстве памяти.

Наш новый код по-прежнему должен взаимодействовать со стандартной библиотекой. Если наш новый код использовал подписанные типы, в то время как стандартная библиотека продолжает использовать неподписанные, нам становится все труднее для каждого потребителя, который должен использовать оба.

Миф 2: вам не нужен этот дополнительный бит. (A.K.A., у вас никогда не будет строки размером более 2 ГБ, если ваше адресное пространство составляет всего 4 ГБ.)

Размеры и индексы относятся не только к памяти. Ваше адресное пространство может быть ограничено, но вы можете обрабатывать файлы, которые намного больше вашего адресного пространства. И хотя у вас может не быть строки с большим объемом 2 ГБ, вы можете с комфортом иметь битсет с более чем 2 Гбит. И не забывайте, что виртуальные контейнеры предназначены для разреженных данных.

Миф 3. Вы всегда можете использовать более широкий тип подписки.

Не всегда. Верно, что для локальной или двух переменных вы можете использовать std::int64_t (при условии, что ваша система имеет один) или signed long long и, вероятно, писать совершенно разумный код. (Но вам все равно понадобятся некоторые явные приемы и проверка в два раза больше, или вам придется отключить некоторые предупреждения компилятора, которые могли бы предупредить вас об ошибках в другом месте вашего кода.)

Но что, если вы строите большую таблицу индексов? Вам действительно нужны дополнительные два или четыре байта для каждого индекса, когда вам нужен только один бит? Даже если у вас много памяти и современный процессор, что делает таблицу в два раза большей, может иметь вредные последствия для локальности ссылки, и все ваши проверки диапазона теперь выполняются с двух шагов, что снижает эффективность прогнозирования ветвлений. А что, если у вас нет всей этой памяти?

Миф 4: Беззнаковая арифметика удивительна и неестественна.

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

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

Это ошибка:

auto mid = (min + max) / 2;  // BUGGY

Если min и max подписаны, сумма может переполняться и что дает поведение undefined. Большинство из нас регулярно пропускают эти ошибки, потому что мы забываем, что дополнение не закрывается по множеству подписанных int. Мы избегаем этого, потому что наши компиляторы обычно генерируют код, который делает что-то разумное (но все же удивительное).

Если min и max не указаны, сумма может все еще переполняться, но поведение undefined исчезло. Вы по-прежнему получите неправильный ответ, поэтому он все еще удивляет, но не более неожиданным, чем с подписанными ints.

Настоящий неподписанный сюрприз приходит с вычитанием: если вы вычтите большее значение unsigned int из меньшего, вы получите в итоге большое число. Этот результат не является более неожиданным, чем если бы вы разделились на 0.

Даже если вы можете устранить неподписанные типы из всех ваших API, вы все равно должны быть готовы к этим неподписанным "неожиданностям", если имеете дело со стандартными контейнерами или файловыми форматами или проводными протоколами. Действительно ли стоит добавить трение к вашим API, чтобы "решить" только часть проблемы?