У меня очень простой вопрос, который меня долго расстраивает. Я имею дело с сетями и базами данных, поэтому большое количество данных, с которыми я имею дело, это 32-битные и 64-битные счетчики (без знака), 32-битные и 64-битные идентификаторы (также не имеют значимого отображения для знака). Я практически никогда не имею дело с реальным словом, которое можно выразить как отрицательное число.
Я и мои коллеги обычно используем неподписанные типы, как uint32_t
и uint64_t
для этих вопросов, и поскольку это происходит так часто, мы также используем их для индексов массивов и других распространенных целочисленных применений.
В то же время различные руководства по кодированию, которые я читаю (например, Google), не поощряют использование целочисленных типов без знака, и, насколько мне известно, ни Java, ни Scala не имеют целочисленных типов без знака.
Итак, я не мог понять, что правильно делать: использование подписанных значений в нашей среде было бы очень неудобно, в то же время руководства по кодированию настаивали на том, чтобы делать именно это.
источник
Ответы:
Есть две школы мысли об этом, и ни одна из них никогда не согласится.
Первый утверждает, что есть некоторые концепции, которые по своей природе не имеют знака, такие как индексы массива. Для них нет смысла использовать номера со знаком, так как это может привести к ошибкам. Это также может накладывать ненужные ограничения на вещи - массив, использующий 32-разрядные индексы со знаком, может получить доступ только к 2 миллиардам записей, в то время как переключение на 32-разрядные числа без знака допускает 4 миллиарда записей.
Вторая утверждает, что в любой программе, которая использует числа без знака, рано или поздно вы будете выполнять смешанную арифметику со знаком без знака. Это может дать странные и неожиданные результаты: приведение большого значения без знака к знаку дает отрицательное число, и наоборот, приведение отрицательного числа к знаку дает большое положительное число. Это может быть большим источником ошибок.
источник
int
короче набирать :)int
более чем достаточно для индексов массива 99,99% раз. Арифметические проблемы со знаком и без знака встречаются гораздо чаще и, таким образом, имеют приоритет с точки зрения того, чего следует избегать. Да, компиляторы предупреждают вас об этом, но сколько предупреждений вы получаете при компиляции любого значительного проекта? Игнорирование предупреждений опасно и является плохой практикой, но в реальном мире ...size_t
, если только нет особого случая. хорошая причина в противном случае.Прежде всего, руководству по кодированию Google C ++ не очень хорошо следовать: оно избегает таких вещей, как исключения, повышение и т. Д., Которые являются основными в современном C ++. Во-вторых, только то, что определенное руководство работает для компании X, не означает, что оно будет подходящим для вас. Я бы продолжил использовать неподписанные типы, так как они вам нужны.
Правильное практическое правило для C ++: предпочитайте,
int
если у вас нет веских причин использовать что-то еще.источник
return false
если этот инвариант не установлен. Таким образом, вы можете либо разделить вещи и использовать функции init для своих объектов, либо вы можете создать astd::runtime_error
, позволить разматыванию стека произойти, и позволить всем вашим объектам RAII автоматически очистить себя, и вы, разработчик, можете обработать исключение, где это удобно для Вы должны сделать это.nullptr
? вернуть объект по умолчанию (что бы это ни значило)? Вы ничего не решили - вы просто спрятали проблему под ковер и надеетесь, что никто не узнает.signal(6)
? Если вы используете исключение, 50% разработчиков, которые знают, как с ними обращаться, могут написать хороший код, а остальное может перенести их сверстник.В других ответах отсутствуют примеры из реальной жизни, поэтому я добавлю один. Одна из причин, по которой я (лично) стараюсь избегать неподписанных типов.
Рассмотрим использование стандартного size_t в качестве индекса массива:
ОК, совершенно нормально. Затем рассмотрим, что мы решили изменить направление цикла по некоторым причинам:
И сейчас это не работает. Если бы мы использовали
int
в качестве итератора, не было бы никаких проблем. Я видел такую ошибку дважды за последние два года. Однажды это случилось в производстве и было трудно отлаживать.Еще одна причина для меня - раздражающие предупреждения, которые заставляют вас писать что-то подобное каждый раз :
Это мелочи, но они складываются. Я чувствую, что код чище, если везде используются только целые числа со знаком.
Редактировать: Конечно, примеры выглядят глупо, но я видел людей, делающих эту ошибку. Если есть такой простой способ избежать этого, почему бы не использовать его?
Когда я компилирую следующий фрагмент кода с VS2015 или GCC, я не вижу предупреждений с настройками предупреждений по умолчанию (даже с -Wall для GCC). Вы должны попросить -Wextra, чтобы получить предупреждение об этом в GCC. Это одна из причин, по которой вы всегда должны компилировать с Wall и Wextra (и использовать статический анализатор), но во многих реальных проектах люди этого не делают.
источник
for (size_t i = n - 1; i < n; --i)
чтобы заставить его работать правильно.size_t
в обратном порядке, есть руководство по кодированию в стилеfor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
он достаточно большой, чтобы содержать все допустимые значенияsize_t
. В частности,int
может разрешать числа только до 2 ^ 15-1, и обычно это делает в системах, которые имеют пределы выделения памяти 2 ^ 16 (или в некоторых случаях даже выше).long
может быть безопаснее сделать ставку, хотя все еще не гарантированно работать. Толькоsize_t
гарантированно работает на всех платформах и во всех случаях.Проблема здесь в том, что вы написали цикл недобросовестным способом, приводящим к ошибочному поведению. Построение цикла, как начинающие получить его преподавали подписанные типов (это нормально и правильно) , но он просто не подходит для значений без знака. Но это не может служить контраргументом против использования неподписанных типов, задача здесь состоит в том, чтобы просто сделать ваш цикл правильным. И это легко исправить, чтобы надежно работать с неподписанными типами, например так:
Это изменение просто отменяет последовательность операций сравнения и декремента и, на мой взгляд, является наиболее эффективным, бесперебойным, чистым и сокращающим способом обработки счетчиков без знака в обратных циклах. Вы бы сделали то же самое (интуитивно) при использовании цикла while:
Отсутствие переполнения может произойти, случай с пустым контейнером скрыт неявно, как в хорошо известном варианте для цикла счетчика со знаком, и тело цикла может остаться неизменным по сравнению со счетчиком со знаком или циклом пересылки. Вы просто должны привыкнуть к сначала несколько странно выглядящей конструкции цикла. Но после того, как вы видели это дюжину раз, больше нет ничего неразборчивого.
Я был бы счастлив, если бы курсы начинающих не только показывали правильный цикл для подписанных, но и для неподписанных типов. Это позволило бы избежать пары ошибок, которые ИМХО следует обвинить невольным разработчикам вместо того, чтобы обвинять неподписанный тип.
НТН
источник
Целые числа без знака существуют по причине.
Рассмотрим, например, обработку данных в виде отдельных байтов, например, в сетевом пакете или файловом буфере. Иногда вы можете встретить таких зверей, как 24-битные целые числа. Легко сдвигается по битам из трех 8-разрядных целых чисел без знака, не так просто с 8-разрядными целыми числами со знаком.
Или подумайте об алгоритмах, используя таблицы поиска символов. Если символ представляет собой 8-разрядное целое число без знака, вы можете индексировать таблицу поиска по значению символа. Однако что делать, если язык программирования не поддерживает целые числа без знака? Вы бы имели отрицательные индексы для массива. Ну, я думаю, вы могли бы использовать что-то подобное,
charval + 128
но это просто ужасно.Фактически, многие форматы файлов используют целые числа без знака, и если язык прикладного программирования не поддерживает целые числа без знака, это может быть проблемой.
Затем рассмотрите порядковые номера TCP. Если вы пишете какой-либо код обработки TCP, вам наверняка захочется использовать целые числа без знака.
Иногда эффективность так важна, что вам действительно нужен этот дополнительный бит без знака. Рассмотрим, например, устройства IoT, которые поставляются миллионами. Многие программные ресурсы могут быть оправданы для микрооптимизации.
Я бы сказал, что компилятор с соответствующими предупреждениями может преодолеть оправдание, позволяющее избежать использования целочисленных типов без знака (арифметика со смешанными знаками, сравнения со смешанными знаками). Такие предупреждения обычно не включены по умолчанию, но смотрите, например,
-Wextra
или отдельно-Wsign-compare
(автоматическое включение в C с помощью-Wextra
, хотя я не думаю, что это автоматическое включение в C ++) и-Wsign-conversion
.Тем не менее, если сомневаетесь, используйте подписанный тип. Много раз, это выбор, который работает хорошо. И включите эти предупреждения компилятора!
источник
Есть много случаев, когда целые числа на самом деле не представляют числа, но, например, битовую маску, идентификатор и т. Д. В основном, случаи, когда добавление 1 к целому числу не дает никакого значимого результата. В этих случаях используйте unsigned.
Есть много случаев, когда вы делаете арифметику с целыми числами. В этих случаях используйте целые числа со знаком, чтобы избежать неправильного поведения около нуля. Посмотрите множество примеров с циклами, где выполнение цикла до нуля либо использует очень неинтуитивный код, либо не работает из-за использования чисел без знака. Есть аргумент «но индексы никогда не бывают отрицательными» - конечно, но различия индексов, например, отрицательны.
В очень редком случае, когда индексы превышают 2 ^ 31, но не 2 ^ 32, вы не используете целые числа без знака, вы используете 64-битные целые числа.
Наконец, хорошая ловушка: в цикле "for (i = 0; i <n; ++ i) a [i] ...", если я 32-разрядный без знака, а память превышает 32-разрядные адреса, компилятор не может оптимизировать доступ к [i] путем увеличения указателя, потому что при i = 2 ^ 32 - 1 я оборачиваюсь. Даже когда n никогда не становится таким большим. Использование целых чисел со знаком позволяет избежать этого.
источник
Наконец, я нашел действительно хороший ответ: «Поваренная книга безопасного программирования» Дж. Виега и М. Мессье ( http://shop.oreilly.com/product/9780596003944.do )
Проблемы безопасности с целыми числами со знаком:
Есть проблемы с подписанными <-> беззнаковыми преобразованиями, поэтому не рекомендуется использовать mix.
источник