Использует ли перехват целых чисел без знака?
На днях я читал стандарт C, и заметил, что в отличие от подписанного целочисленного переполнения (которое не определено), неподписанное целочисленное переполнение хорошо определено. Я видел, что он использовался во многих кодах для максимумов и т. Д., Но, учитывая voodoos о переполнении, считается ли это хорошей практикой программирования? Это в любом случае небезопасно? Я знаю, что многие современные языки, такие как Python, не поддерживают его, вместо этого они продолжают расширять размер больших чисел.
- Захват переменной lambda в цикле - что здесь происходит?
- stl :: multimap - как получить группы данных?
- В чем разница между динамическими (C # 4) и var?
- Вложенные с помощью операторов в C #
- Смыкая свободную функцию
- Как не сериализовать свойство __type на объектах JSON
- Можем ли мы перегружать операторы для встроенных типов, таких как int или float?
- segmentation fault с помощью scanf
Беззнаковое целочисленное переполнение (в форме обертывания) обычно используется в hash-функциях и с момента года.
Один из способов, которым я мог бы думать о неподписанном переполнении целого числа, вызывающем проблему, – это вычитание из небольшого значения без знака, в результате чего он переносится на большое положительное значение.
Практические рекомендации по переполнению целых чисел:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics
Короче говоря:
Совершенно правомерно / нормально / безопасно использовать неподписанное целочисленное переполнение по мере того, как вы считаете нужным, пока вы обращаете внимание и придерживаетесь определения (для любой цели – оптимизация, супер-умные алгоритмы и т. Д.),
Просто потому, что вы знаете, что мелочи стандарта не означает, что человек, поддерживающий ваш код, делает это. Этот человек, возможно, придется тратить время на беспокойство об этом во время отладки позже, или придется искать стандарт, чтобы проверить это поведение позже.
Конечно, мы ожидаем знания разумных особенностей языка у рабочего программиста – и у разных компаний / групп есть другое ожидание того, где это разумное мастерство. Но для большинства групп это кажется немного большим, чтобы ожидать, что следующий человек узнает верхнюю часть головы и не подумает об этом.
Если этого было недостаточно, вы, скорее всего, столкнетесь с ошибками компилятора, когда будете работать по краям стандарта. Или, что еще хуже, пользователь может перенести этот код на новую платформу.
Короче говоря, я голосую, не делай этого!
Я использую его все время, чтобы сказать, пора ли что-то сделать.
UInt32 now = GetCurrentTime() if( now - then > 100 ) { // do something }
Пока вы проверяете значение до ‘now’ laps ‘then’, вы отлично справляетесь со всеми значениями ‘now’ и ‘then’.
EDIT: Я думаю, это действительно мало.
Я бы не стал полагаться на него только по соображениям удобочитаемости. Вы собираетесь отлаживать свой код в течение нескольких часов, прежде чем вы выясните, где вы сбросите эту переменную до 0.
Другое место, где неподписанное переполнение может быть полезно использовать, – это когда вы должны итерации назад от заданного типа unsigned:
void DownFrom( unsigned n ) { unsigned m; for( m = n; m != (unsigned)-1; --m ) { DoSomething( m ); } }
Другие альтернативы не такие аккуратные. Попытка сделать m >= 0
не работает, если вы не измените m на подписанный, но тогда вы можете усечь значение n – или хуже – преобразовать его в отрицательное число при инициализации.
В противном случае вам нужно сделать! = 0 или> 0, а затем вручную выполнить случай 0 после цикла.
Так как подписанные числа на процессорах могут быть представлены по-разному, 99,999% всех текущих процессоров используют двухкомпонентную нотацию. Так как это большинство машин, трудно найти другое поведение, хотя компилятор может проверить его (шанс на жирность). Однако спецификации C должны учитывать 100% компиляторов, поэтому не определили его поведение.
Таким образом, это сделало бы еще больше путаницы, что является хорошей причиной, чтобы избежать этого. Однако, если у вас есть действительно веская причина (скажем, повышение производительности фактора 3 для критической части кода), тогда хорошо документируйте его и используйте.
Это нормально полагаться на переполнение, если вы знаете, КОГДА это произойдет …
У меня, например, были проблемы с реализацией C MD5 при переходе на более поздний компилятор … Кодекс действительно ожидал переполнения, но также ожидал 32 бита ints.
С 64 бит результаты были неправильными!
К счастью, для этого нужны автоматические тесты: рано я поймал проблему, но это могло быть настоящей ужасной историей, если не остаться незамеченной.
Вы могли бы возразить «но это случается редко»: да, но это делает его еще более опасным! Когда есть ошибка, все подозрительно относятся к коду, написанному за последние несколько дней. Никто не является подозрительным кодом, который «просто работал годами», и обычно никто до сих пор не знает, как это работает …
Если вы используете его с умом (хорошо прокомментированный и читаемый), вы можете извлечь из этого выгоду из-за меньшего и быстрого кода.
siukurnin делает хороший момент, что вам нужно знать, когда произойдет переполнение. Самый простой способ избежать проблемы переносимости, которую он описал, – использовать целочисленные типы фиксированной ширины из stdint.h. uint32_t представляет собой неподписанное 32-разрядное целое на всех платформах и операционных системах и не будет вести себя иначе при компиляции для другой системы.
Я бы предложил всегда иметь явное приведение в любое время, когда кто-то будет полагаться на обнуление беззнаковых чисел. В противном случае могут быть сюрпризы. Например, если «int» – 64 бита, код:
UInt32 foo,bar; if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive) ... do something
может выйти из строя, поскольку «foo» и «bar» будут продвигаться до 64-битных целых чисел. Добавление типа обратно в UInt32 до проверки результата против 100 предотвратит проблемы в этом случае.
Кстати, я считаю, что единственный переносимый способ напрямую получить нижние 32 бита продукта из двух UInt32 - это сдать один из ints в UInt64 перед тем, как делать умножение. В противном случае UInt32 может быть преобразован в подписанный Int64, с переполнением умножения и получением неопределенных результатов.