Ответ 1
Второй вопрос: div
- очень медленная инструкция (более 20 тактов). Последовательность выше содержит больше инструкций, но все они относительно быстрые, поэтому это чистая победа с точки зрения скорости.
Первые пять команд (вплоть до shrl
) вычисляют i/10 (я объясню, как через минуту).
Следующие несколько команд снова умножают результат на 10, но избегая инструкций mul
/imul
(независимо от того, является ли это победой или нет, зависит от точного процессора, на который вы нацеливаетесь - новые x86s имеют очень быстрые множители, но более старые не делают).
movl %edx, %eax ; eax=i/10
sall $2, %eax ; eax=(i/10)*4
addl %edx, %eax ; eax=(i/10)*4 + (i/10) = (i/10)*5
addl %eax, %eax ; eax=(i/10)*5*2 = (i/10)*10
Затем вычитается из i
снова, чтобы получить i - (i/10)*10
, который равен i % 10
(для неподписанных номеров).
Наконец, при вычислении i/10: Основная идея - заменить деление на 10 умножением на 1/10. Компилятор делает приближение с фиксированной точкой, умножая на (2 ** 35/10 + 1) - магическое значение, загруженное в edx
, хотя оно выводится как знаковое значение, даже если оно действительно без знака - сдвигая результат на 35. Это, оказывается, дает правильный результат для всех 32-битных целых чисел.
Существуют алгоритмы для определения такого приближения, которые гарантируют, что ошибка меньше 1 (что для целых чисел означает ее правильное значение), и GCC, очевидно, использует один:)
Заключительное замечание. Если вы хотите, чтобы GCC вычислил модуль, сделайте переменную divisor (например, параметр функции), чтобы она не могла выполнять такую оптимизацию. Во всяком случае, на x86 вы вычисляете modulo с помощью div
. div
ожидает 64-битного дивиденда в edx:eax
(высокие 32 бита в edx, минимум 32 бит в eax - четкий edx до нуля, если вы работаете с 32-разрядным номером) и делит это на любой операнд, который вы указываете (например, div ebx
делит edx:eax
на ebx
). Он возвращает коэффициент в eax
, а остаток - в edx
. idiv
делает то же самое для подписанных значений.