Использует ли перехват целых чисел без знака?

Я читал стандарт C на днях, и заметил, что в отличие от подписанного целочисленного переполнения (который является undefined) полнозначное переполнение без знака четко определено. Я видел, что он использовался во многих кодах для максимумов и т.д., Но, учитывая voodoos о переполнении, считается ли это хорошей практикой программирования? Это в любом случае небезопасно? Я знаю, что многие современные языки, такие как Python, не поддерживают его, вместо этого они продолжают расширять размер больших чисел.

Ответ 1

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

Ответ 2

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

Практический совет для переполнения целых чисел:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics

Ответ 3

Короче говоря:

Совершенно правомерно/ОК/безопасно использовать неподписанное целочисленное переполнение, как вы считаете нужным, пока вы обращаете внимание и придерживаетесь определения (для любой цели - оптимизация, супер-умные алгоритмы и т.д.)я >

Ответ 4

Просто потому, что вы знаете, что мелочи стандарта не означают, что человек, поддерживающий ваш код, делает это. Этому человеку, возможно, придется тратить время на беспокойство об этом при отладке позже, или придется искать стандарт, чтобы проверить это поведение позже.

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

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

Короче говоря, я голосую, не делай этого!

Ответ 5

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

UInt32 now = GetCurrentTime()

if( now - then > 100 )
{
   // do something
}

Пока вы проверяете значение до 'now' laps 'then', вы отлично справляетесь со всеми значениями 'now' и 'then'.

EDIT: Я думаю, что это действительно недоиспользование.

Ответ 6

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

Ответ 7

Другое место, где неподписанное переполнение может быть полезно использовать, - это когда вы должны итерации назад от заданного типа без знака:

void DownFrom( unsigned n )
{
    unsigned m;

    for( m = n; m != (unsigned)-1; --m )
    {
        DoSomething( m );
    }
}

Другие альтернативы не такие аккуратные. Попытка сделать m >= 0 не работает, если вы не измените m на подписанный, но тогда вы можете усечь значение n - или хуже - преобразовать его в отрицательное число при инициализации.

В противном случае вам нужно сделать!= 0 или > 0, а затем вручную выполнить случай 0 после цикла.

Ответ 8

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

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

Ответ 9

Хорошо, полагаться на переполнение, пока вы знаете, КОГДА это произойдет...

У меня, например, были проблемы с реализацией C MD5 при переходе на более поздний компилятор... Код ожидал переполнения, но ожидал также 32 битных ints.

С 64 бит результаты были неправильными!

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

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

Ответ 10

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

Ответ 11

siukurnin делает хороший момент, что вам нужно знать, когда произойдет переполнение. Самый простой способ избежать проблемы переносимости, которую он описал, - использовать целочисленные типы фиксированной ширины из stdint.h. uint32_t представляет собой 32-битное целое без знака на всех платформах и операционных системах и не будет вести себя по-разному при компиляции для другой системы.

Ответ 12

Я предлагаю всегда иметь явное приведение в любое время, когда кто-то будет полагаться на упаковку без знаковых чисел. В противном случае могут быть сюрпризы. Например, если "int" - 64 бита, код типа:

UInt32 foo,bar;
if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive)
  ... do something

может потерпеть неудачу, так как "foo" и "bar" будут повышены до 64-разрядных целых чисел. Добавление typecast обратно в UInt32 перед проверкой результата против 100 предотвратит проблемы в этом случае.

Кстати, я считаю, что единственный переносимый способ напрямую получить нижние 32 бита продукта из двух UInt32 состоит в том, чтобы сдать один из ints в UInt64 перед тем, как выполнить умножение. В противном случае UInt32 может быть преобразован в подписанный Int64, с переполнением умножения и получением результатов undefined.