Второй вопрос первый: div
- очень медленная инструкция (более 20 тактов). Вышеприведенная последовательность состоит из большего количества инструкций, но все они относительно быстрые, так что это чистый выигрыш с точки зрения скорости.
Первые пять инструкций (до shrl
включительно) вычисляют i / 10 (я объясню, как через минуту).
Следующие несколько инструкций снова умножают результат на 10, но избегая инструкций mul
/ imul
(независимо от того, выиграл он или нет, зависит от того, на какой процессор вы ориентируетесь - более новые x86 имеют очень быстрые множители, но старшие не делают).
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 вычислялся по модулю, задайте переменную делителя (например, параметр функции), чтобы он не мог выполнять такую оптимизацию. В любом случае на x86 вы вычисляете по модулю, используя div
. div
ожидает 64-битное деление в edx:eax
(старшие 32 бита в edx, младшие 32 бита в eax - обнулять edx в ноль, если вы работаете с 32-битным числом) и делит его на любой операнд, который вы укажете (например, div ebx
делит edx:eax
на ebx
). Возвращает частное в eax
, а остаток в edx
. idiv
делает то же самое для значений со знаком.