Почему предпочитают подписываться без знака на С++?

Я хотел бы лучше понять, почему выбрать int over unsigned?

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

Я спрашиваю об этом, потому что и Бьярне Страуструп, и Чандлер Каррут дали совет предпочесть int над unsigned здесь (приблизительно 12:30).

Я могу видеть, что аргумент использования int over short или long - int является "самой естественной" шириной данных для целевой архитектуры машины.

Но подписанный беззнаковый всегда меня раздражал. Значительно быстрее подписали ценности на типичных современных архитектурах процессора? Что делает их лучше?

Ответ 1

Позвольте мне перефразировать видео, поскольку эксперты сказали это лаконично.

Андрей Александреску:

  • Нет простого руководства.
  • В системном программировании нам нужны целые числа разных размеров и подписей.
  • Многие преобразования и тайные правила определяют арифметику (например, для auto), поэтому мы должны быть осторожны.

Чандлер Каррут:

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

Бьярне Страуструп:

  • Используйте int, пока у вас не будет причины не делать этого.
  • Используйте неподписанные только для битовых шаблонов.
  • Никогда не смешивайте подписанные и неподписанные

Прислушивайтесь к правилам подписки, мое одно предложение отнимет у экспертов:

Используйте соответствующий тип, и, когда вы не знаете, используйте int, пока не узнаете.

Ответ 2

В соответствии с запросами в комментариях: я предпочитаю int вместо unsigned, потому что...

  • он короче (я серьезно!)

  • он более общий и более интуитивно понятный (например, мне нравится предполагать, что 1 - 2 равно -1, а не какое-то непонятное огромное количество)

  • Что делать, если я хочу сигнализировать об ошибке, возвращая значение вне диапазона?

Конечно, есть встречные аргументы, но это основные причины, по которым я хочу объявить свои целые числа как int вместо unsigned. Конечно, это не всегда так, в других случаях, unsigned - просто лучший инструмент для задачи, я просто отвечаю на вопрос "почему кто-то предпочитает нестандартную подпись".

Ответ 3

Несколько причин:

  • Арифметика на unsigned всегда дает unsigned, что может быть проблемой при вычитании целочисленных величин, которые могут разумно привести к отрицательному результату - подумайте о вычитании денежных величин, чтобы получить баланс, или индексы массива, чтобы дать расстояние между элементами. Если операнды без знака, вы получите совершенно определенный, но почти наверняка бессмысленный результат, а сравнение result < 0 всегда будет ложным (о чем, к счастью, вас предупреждают современные компиляторы).

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

Ответ 4

Нет причин предпочитать signed over unsigned, помимо чисто социологических, то есть некоторые считают, что средние программисты не компетентны и/или достаточно внимательны, чтобы писать правильный код в терминах типов unsigned. Это часто является основным аргументом, используемым различными "динамиками", независимо от того, насколько уважаемы эти ораторы.

В действительности, компетентные программисты быстро развивают и/или изучают базовый набор идиом и навыков программирования, которые позволяют им писать правильный код с точки зрения неподписанных интегральных типов.

Отметим также, что фундаментальные различия между подписанной и неподписанной семантикой всегда присутствуют (в внешне различной форме) в других частях языка C и С++, таких как арифметика указателей и арифметика итератора. Это означает, что в общем случае у программиста нет возможности избежать проблем, связанных с неподписанной семантикой, и "проблем", которые она несет с собой. То есть хотите ли вы этого или нет, вам нужно научиться работать с диапазонами, которые внезапно заканчиваются на их левом конце и заканчиваются здесь (не где-то на расстоянии), даже если вы непреклонно избегаете целых чисел unsigned.

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

Единственная реальная причина, чтобы предпочесть signed в некоторых контекстах, которые приходит на ум, заключается в том, что в смешанном коде с целочисленным/с плавающей точкой signed целочисленные форматы обычно напрямую поддерживаются набором команд FPU, тогда как форматы unsigned не являются вообще говоря, что позволяет компилятору генерировать дополнительный код для конверсий между значениями с плавающей запятой и значениями unsigned. В таком коде signed типы могут работать лучше.

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

В своей практике я всегда придерживаюсь unsigned везде, где могу, и использую signed, только если мне действительно нужно.

Ответ 5

Интегральные типы в C и многие языки, которые вытекают из него, имеют два общих случая использования: представлять числа или представлять элементы абстрактного алгебраического кольца. Для тех, кто не знаком с абстрактной алгеброй, первичное понятие за кольцом заключается в том, что добавление, вычитание или умножение двух элементов кольца должно давать другой элемент этого кольца - он не должен терпеть крах или давать значение вне кольца. На 32-битной машине добавление unsigned 0x12345678 в unsigned 0xFFFFFFFF не "переполняется" - оно просто дает результат 0x12345677, который определен для кольца целых чисел, сравнимого с mod 2 ^ 32 (поскольку арифметический результат добавления 0x12345678 в 0xFFFFFFFF, т.е. 0x112345677, соответствует 0x12345677 mod 2 ^ 32).

Концептуально обе цели (представляющие числа или представляющие члены кольца целых чисел, совпадающие с mod 2 ^ n) могут обслуживаться как подписанными, так и неподписанными типами, а многие операции одинаковы для обоих случаев использования, но есть некоторые различия. Между прочим, попытка добавить два числа не должна давать ничего, кроме правильной арифметической суммы. Хотя вопрос о том, должен ли язык требовать генерировать код, необходимый для того, чтобы гарантировать его отсутствие (например, исключение будет выбрано вместо этого), можно утверждать, что для кода, который использует интегральные типы для представления чисел, такое поведение было бы предпочтительным чтобы получить арифметически неправильное значение, а компиляторы не должны быть запрещены таким образом.

Разработчики стандартов C решили использовать подписанные целочисленные типы для представления чисел и неподписанных типов для представления членов алгебраического кольца целых чисел, совпадающих с mod 2 ^ n. Напротив, Java использует знаковые целые числа для представления членов таких колец (хотя в некоторых контекстах они интерпретируются по-разному, причем конверсии между типами подписанного типа различного типа ведут себя не так, как среди беззнаковых), а Java не имеет ни целых без знака, ни каких-либо примитивные интегральные типы, которые ведут себя как числа во всех не исключительных случаях.

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

Кстати, причина, по которой (uint32_t) -1 равна 0xFFFFFFFF, проистекает из того факта, что приведение знакового значения в unsigned эквивалентно добавлению беззнакового нуля, а добавление целого к неподписанному значению определяется как добавление или вычитание его величины в/из значения без знака в соответствии с правилами алгебраического кольца, которые указывают, что если X = YZ, то X является единственным и единственным членом этого кольца, такое X + Z = Y. В беззнаковой математике 0xFFFFFFFF - это единственный номер, который при добавлении в unsigned 1 дает беззнаковый нуль.

Ответ 6

Скорость на современных архитектурах одинакова. Проблема с unsigned int заключается в том, что иногда она может вызвать неожиданное поведение. Это может создать ошибки, которые не отображались бы иначе.

Обычно, когда вы вычитаете 1 из значения, значение становится меньше. Теперь с переменными signed и unsigned int будет время, когда вычитание 1 создает значение, которое является MUCH LARGER. Ключевое различие между unsigned int и int заключается в том, что при unsigned int значение, генерирующее парадоксальный результат, является обычно используемым значением --- 0 --- тогда как с подписанным число безопасно удалено от обычных операций.

Что касается возврата -1 для значения ошибки - современное мышление заключается в том, что лучше исключить исключение, чем проверять значения возврата.

Верно, что если вы правильно защищаете свой код, у вас не будет этой проблемы, и если вы будете использовать неподписанные религиозно повсюду, вы будете в порядке (при условии, что вы только добавляете и не вычитаете, и что вы никогда не приближаетесь к MAX_INT). Я использую unsigned int всюду. Но это требует много дисциплины. Для множества программ вы можете использовать int и тратить время на другие ошибки.

Ответ 7

Чтобы ответить на реальный вопрос: для огромного количества вещей это не имеет большого значения. int может быть немного легче иметь дело с такими вещами, как вычитание со вторым операндом, большим, чем первое, и вы все равно получите "ожидаемый" результат.

В 99,9% случаев абсолютно нет разницы в скорости, потому что ТОЛЬКО инструкции, которые отличаются для подписанных и неподписанных номеров:

  • Увеличение числа (заполнение значком для знака или ноль для без знака) - для выполнения обоих задач требуется одинаковое усилие.
  • Сравнения - подписанное число, процессор должен принимать во внимание, если либо номер отрицательный, либо нет. Но опять же, это та же самая скорость, чтобы сравнить с подписанными или неподписанными числами - просто используя другой код команды, чтобы сказать, что "номера с самым высоким битом меньше, чем числа с самым высоким битом, не установленным" (по существу). [Педантично, почти всегда операция, использующая РЕЗУЛЬТАТ сравнения, которая отличается - наиболее распространенным случаем является условный переход или инструкция ветвления, - но в любом случае это то же самое усилие, что и входы воспринимаются как несколько разные вещи ].
  • Умножьте и разделите. Очевидно, что преобразование знака результата должно происходить, если это подписанное умножение, где unsigned не должно изменять знак результата, если установлен старший бит одного из входов. И снова, усилия (так близко, как мы заботимся) идентичны.

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

Ответ 8

  • Использовать int по умолчанию: он играет лучше с остальной частью языка

    • Наиболее распространенным использованием домена является регулярная арифметика, а не модульная арифметика.
    • int main() {} // see an unsigned?
    • auto i = 0; // i is of type int
  • Используйте только unsigned для модульной арифметики и бит-twiddling (в частности, для переключения)

    • имеет разную семантику, чем обычная арифметика, убедитесь, что это то, что вы хотите.
    • бит-сдвиг подписанных типов тонкий (см. комментарии от @ChristianRau)
    • если вам нужен вектоp > 2Gb на 32-разрядной машине, обновите свою ОС/оборудование
  • Никогда не смешивайте арифметику с подписью и без знака

    • правила для этого сложные и удивительные (любой может быть преобразован в другой, в зависимости от относительных размеров типоразмеров)
    • включить -Wconversion -Wsign-conversion -Wsign-promo (gcc лучше, чем Clang здесь)
    • Стандартная библиотека ошиблась с std::size_t (цитата из видео GN13)
    • используйте диапазон-для, если можете,
    • for(auto i = 0; i < static_cast<int>(v.size()); ++i), если вы должны
  • Не используйте короткие или большие типы, если они вам вообще не нужны

    • текущий поток данных архитектуры хорошо подходит для 32-разрядных данных без указателя (но обратите внимание на комментарий @BenVoigt о свойствах кеша для меньших типов).
    • char и short сэкономить место, но страдают от целых рекламных акций
    • Вы действительно будете рассчитывать на все int64_t?

Ответ 9

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

Если вам нужен больший диапазон, используйте 64-битное целое число.

Если вы выполняете итерацию с использованием индексов, типы обычно имеют size_type, и вам не важно, подписана она или нет.

Скорость не является проблемой.

Ответ 10

Тип int более похож на поведение математических целых чисел, чем тип unsigned.

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

Проблема заключается в том, что тип unsigned имеет разрывное поведение рядом с нулем. Любая операция, которая пытается вычислить небольшое отрицательное значение, вместо этого создает некоторое большое положительное значение. (Хуже: тот, который определяется реализацией.)

Алгебраические отношения, такие как a < b, подразумевают, что a - b < 0 разрушаются в неподписанном домене даже для небольших значений, таких как a = 3 и b = 4.

Нисходящий цикл, такой как for (i = max - 1; i >= 0; i--), не завершается, если i создается без знака.

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

Достоинство неподписанных типов заключается в том, что определенные операции, которые не определены на уровне бит для подписанных типов, не соответствуют этим типам для неподписанных типов. В неподписанных типах отсутствует знаковый бит, поэтому смещение и маскировка через битовый знак не является проблемой. Беззнаковые типы хороши для битмаски, а для кода, который реализует точную арифметику независимым от платформы способом. Unsigned opearations будут имитировать две семантики дополнения даже на машине без двух дополнений. Для записи многоточечной библиотеки (bignum) практически необходимо использовать массивы беззнаковых типов для представления, а не для подписанных типов.

Беззнаковые типы также подходят в ситуациях, когда числа ведут себя как идентификаторы, а не как арифметические типы. Например, адрес IPv4 может быть представлен в 32-разрядном неподписанном типе. Вы не добавляли бы адреса IPv4.

Ответ 11

Одна хорошая причина, о которой я могу думать, - это в случае обнаружения переполнения.

Для таких случаев использования, как количество элементов в массиве, длина строки или размер блока памяти, вы можете переполнить unsigned int, и вы можете не заметить разницы, даже если вы посмотрите на переменную, Если это подписанный int, переменная будет меньше нуля и явно ошибочна.

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

Ответ 12

Для меня, помимо всех целых чисел в диапазоне 0.. + 2,147,483,647, содержащихся в наборе целых чисел с подписью и без знака в 32-битных архитектурах, существует более высокая вероятность того, что мне нужно будет использовать -1 (или меньше), чем нужно использовать +2,147,483,648 (или больше).

Ответ 13

Это дает неожиданный результат при выполнении простой арифметической операции:

unsigned int i;
i = 1 - 2;
//i is now 4294967295 on a 64bit machine

Это дает неожиданный результат при простом сравнении:

unsigned int j = 1;
std::cout << (j>-1) << std::endl;
//output 0 as false but 1 is greater than -1

Это происходит потому, что при выполнении вышеперечисленных операций подписанные ints преобразуются в unsigned и переполняются и переходят на действительно большое число.