Это зависит. Это может произойти в любом случае, в зависимости от того, что вы делаете с этим int
, а также от свойств базового оборудования.
Очевидным примером в пользу unsigned int
является целочисленная операция деления. В C / C ++ целочисленное деление должно округлять до нуля , тогда как машинное целочисленное деление на x86 округляет в сторону отрицательной бесконечности. Кроме того, различные «оптимизированные» замены для целочисленного деления (сдвиги и т. Д.) Также обычно округляются к отрицательной бесконечности. Таким образом, чтобы удовлетворить стандартные требования, компилятор вынужден корректировать подписанные результаты целочисленного деления с помощью дополнительных машинных инструкций. В случае целочисленного деления без знака эта проблема не возникает, поэтому, как правило, целочисленное деление работает гораздо быстрее для типов без знака, чем для типов со знаком.
Например, рассмотрим это простое выражение
rand() / 2
Код, сгенерированный для этого выражения компилятором MSVC, обычно будет выглядеть следующим образом
call rand
cdq
sub eax,edx
sar eax,1
Обратите внимание, что вместо одной инструкции сдвига (sar
) мы видим здесь целый набор инструкций, т. Е. Нашему sar
предшествуют две дополнительные инструкции (cdq
и sub
). Эти дополнительные инструкции предназначены только для «корректировки» деления, чтобы заставить его генерировать «правильный» (с точки зрения языка Си) результат. Обратите внимание, что компилятор не знает, что ваше значение всегда будет положительным, поэтому он должен генерировать эти инструкции всегда, безоговорочно. Они никогда не будут делать ничего полезного, тратя таким образом время процессора.
Не смотрите на код для
(unsigned) rand() / 2
Это просто
call rand
shr eax,1
В этом случае один сдвиг добился цели, предоставив нам астрономически более быстрый код (только для деления).
С другой стороны, когда вы смешиваете целочисленную арифметику и арифметику с плавающей точкой FPU, целочисленные типы со знаком могут работать быстрее, поскольку набор инструкций FPU содержит непосредственную инструкцию для загрузки / хранения целочисленных значений со знаком, но не имеет инструкций для целого числа без знака значения.
Чтобы проиллюстрировать это, можно использовать следующую простую функцию
double zero() { return rand(); }
Сгенерированный код, как правило, будет очень простым
call rand
mov dword ptr [esp],eax
fild dword ptr [esp]
Но если мы изменим нашу функцию на
double zero() { return (unsigned) rand(); }
сгенерированный код изменится на
call rand
test eax,eax
mov dword ptr [esp],eax
fild dword ptr [esp]
jge zero+17h
fadd qword ptr [__real@41f0000000000000 (4020F8h)]
Этот код заметно больше, потому что набор инструкций FPU не работает с целыми типами без знака, поэтому после загрузки значения без знака необходимы дополнительные корректировки (что и делает это условное fadd
).
Существуют и другие контексты и примеры, которые можно использовать для демонстрации того, что это работает в любом случае. Итак, опять все зависит. Но, как правило, все это не будет иметь значения для общей картины эффективности вашей программы. Я обычно предпочитаю использовать типы без знака для представления количества без знака. В моем коде 99% целочисленных типов без знака. Но я делаю это из чисто концептуальных соображений, а не из-за увеличения производительности.